From 2fc31bdd29f123c7c50e70f1c7967506825dc774 Mon Sep 17 00:00:00 2001 From: Kimon Tsinteris Date: Sun, 30 Mar 2014 15:34:40 -0700 Subject: [PATCH] initial commit --- .gitignore | 15 + CONTRIBUTING.md | 28 + .../Applications/Application-OSX.xcconfig | 20 + .../Applications/Application-iOS.xcconfig | 23 + Configuration/Base-OSX.xcconfig | 12 + Configuration/Base-iOS.xcconfig | 12 + Configuration/Platform/Compiler.xcconfig | 36 + Configuration/Platform/OSX.xcconfig | 18 + Configuration/Platform/iOS.xcconfig | 18 + Configuration/Projects/Project-Debug.xcconfig | 25 + Configuration/Projects/Project-GCOV.xcconfig | 13 + .../Projects/Project-Profile.xcconfig | 31 + .../Projects/Project-Release.xcconfig | 25 + Configuration/Projects/Project.xcconfig | 48 + .../StaticLibrary-OSX.xcconfig | 27 + .../StaticLibrary-iOS.xcconfig | 27 + Configuration/Tests/ApplicationTests.xcconfig | 16 + Configuration/Tests/LogicTests-OSX.xcconfig | 23 + Configuration/Tests/LogicTests-iOS.xcconfig | 26 + Images/pop.gif | Bin 0 -> 182963 bytes Images/pop.png | Bin 0 -> 9643 bytes LICENSE | 30 + PATENTS | 23 + Podfile | 10 + Podfile.lock | 10 + README.md | 149 ++ pop-tests-osx/en.lproj/InfoPlist.strings | 2 + pop-tests-osx/pop-tests-OSX-Info.plist | 22 + pop-tests-osx/pop-tests-OSX-Prefix.pch | 9 + pop-tests/POPAnimatable.h | 26 + pop-tests/POPAnimatable.mm | 74 + pop-tests/POPAnimatablePropertyTests.mm | 115 ++ pop-tests/POPAnimationMRRTests.mm | 88 + pop-tests/POPAnimationTests.mm | 616 +++++++ pop-tests/POPAnimationTestsExtras.h | 18 + pop-tests/POPAnimationTestsExtras.mm | 46 + pop-tests/POPBaseAnimationTests.h | 39 + pop-tests/POPBaseAnimationTests.mm | 76 + pop-tests/POPCustomAnimationTests.mm | 161 ++ pop-tests/POPDecayAnimationTests.mm | 462 +++++ pop-tests/POPEaseInEaseOutAnimationTests.mm | 91 + pop-tests/POPSpringAnimationTests.mm | 594 ++++++ pop-tests/en.lproj/InfoPlist.strings | 2 + pop-tests/pop-tests-Info.plist | 22 + pop-tests/pop-tests-Prefix.pch | 10 + pop.podspec | 16 + pop.xcodeproj/project.pbxproj | 1631 +++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/xcschemes/pop-osx.xcscheme | 69 + .../xcshareddata/xcschemes/pop.xcscheme | 97 + pop.xcworkspace/contents.xcworkspacedata | 10 + pop/POP.h | 29 + pop/POPAction.h | 66 + pop/POPAnimatableProperty.h | 144 ++ pop/POPAnimatableProperty.mm | 622 +++++++ pop/POPAnimation.h | 134 ++ pop/POPAnimation.mm | 228 +++ pop/POPAnimationEvent.h | 68 + pop/POPAnimationEvent.mm | 101 + pop/POPAnimationEventInternal.h | 41 + pop/POPAnimationExtras.h | 43 + pop/POPAnimationExtras.mm | 117 ++ pop/POPAnimationInternal.h | 485 +++++ pop/POPAnimationPrivate.h | 16 + pop/POPAnimationRuntime.h | 102 ++ pop/POPAnimationRuntime.mm | 270 +++ pop/POPAnimationTracer.h | 60 + pop/POPAnimationTracer.mm | 184 ++ pop/POPAnimationTracerInternal.h | 91 + pop/POPAnimator.h | 47 + pop/POPAnimator.mm | 536 ++++++ pop/POPAnimatorPrivate.h | 58 + pop/POPBasicAnimation.h | 71 + pop/POPBasicAnimation.mm | 90 + pop/POPBasicAnimationInternal.h | 88 + pop/POPCGUtils.h | 78 + pop/POPCGUtils.mm | 75 + pop/POPCustomAnimation.h | 46 + pop/POPCustomAnimation.mm | 53 + pop/POPDecayAnimation.h | 54 + pop/POPDecayAnimation.mm | 123 ++ pop/POPDecayAnimationInternal.h | 117 ++ pop/POPDefines.h | 29 + pop/POPGeometry.h | 53 + pop/POPGeometry.mm | 67 + pop/POPLayerExtras.h | 196 ++ pop/POPLayerExtras.mm | 268 +++ pop/POPMath.h | 56 + pop/POPMath.mm | 82 + pop/POPPropertyAnimation.h | 65 + pop/POPPropertyAnimation.mm | 105 ++ pop/POPPropertyAnimationInternal.h | 358 ++++ pop/POPSpringAnimation.h | 67 + pop/POPSpringAnimation.mm | 164 ++ pop/POPSpringAnimationInternal.h | 131 ++ pop/POPSpringSolver.h | 190 ++ pop/POPVector.h | 366 ++++ pop/POPVector.mm | 283 +++ pop/WebCore/FloatConversion.h | 56 + pop/WebCore/TransformationMatrix.cpp | 1072 +++++++++++ pop/WebCore/TransformationMatrix.h | 277 +++ pop/WebCore/UnitBezier.h | 123 ++ pop/en.lproj/InfoPlist.strings | 2 + pop/pop-Info.plist | 30 + pop/pop-Prefix.pch | 7 + 105 files changed, 13252 insertions(+) create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 Configuration/Applications/Application-OSX.xcconfig create mode 100644 Configuration/Applications/Application-iOS.xcconfig create mode 100644 Configuration/Base-OSX.xcconfig create mode 100644 Configuration/Base-iOS.xcconfig create mode 100644 Configuration/Platform/Compiler.xcconfig create mode 100644 Configuration/Platform/OSX.xcconfig create mode 100644 Configuration/Platform/iOS.xcconfig create mode 100644 Configuration/Projects/Project-Debug.xcconfig create mode 100644 Configuration/Projects/Project-GCOV.xcconfig create mode 100644 Configuration/Projects/Project-Profile.xcconfig create mode 100644 Configuration/Projects/Project-Release.xcconfig create mode 100644 Configuration/Projects/Project.xcconfig create mode 100644 Configuration/Static Libraries/StaticLibrary-OSX.xcconfig create mode 100644 Configuration/Static Libraries/StaticLibrary-iOS.xcconfig create mode 100644 Configuration/Tests/ApplicationTests.xcconfig create mode 100644 Configuration/Tests/LogicTests-OSX.xcconfig create mode 100644 Configuration/Tests/LogicTests-iOS.xcconfig create mode 100644 Images/pop.gif create mode 100644 Images/pop.png create mode 100644 LICENSE create mode 100644 PATENTS create mode 100755 Podfile create mode 100644 Podfile.lock create mode 100644 README.md create mode 100644 pop-tests-osx/en.lproj/InfoPlist.strings create mode 100644 pop-tests-osx/pop-tests-OSX-Info.plist create mode 100644 pop-tests-osx/pop-tests-OSX-Prefix.pch create mode 100644 pop-tests/POPAnimatable.h create mode 100644 pop-tests/POPAnimatable.mm create mode 100644 pop-tests/POPAnimatablePropertyTests.mm create mode 100644 pop-tests/POPAnimationMRRTests.mm create mode 100644 pop-tests/POPAnimationTests.mm create mode 100644 pop-tests/POPAnimationTestsExtras.h create mode 100644 pop-tests/POPAnimationTestsExtras.mm create mode 100644 pop-tests/POPBaseAnimationTests.h create mode 100644 pop-tests/POPBaseAnimationTests.mm create mode 100644 pop-tests/POPCustomAnimationTests.mm create mode 100644 pop-tests/POPDecayAnimationTests.mm create mode 100644 pop-tests/POPEaseInEaseOutAnimationTests.mm create mode 100644 pop-tests/POPSpringAnimationTests.mm create mode 100644 pop-tests/en.lproj/InfoPlist.strings create mode 100644 pop-tests/pop-tests-Info.plist create mode 100644 pop-tests/pop-tests-Prefix.pch create mode 100644 pop.podspec create mode 100644 pop.xcodeproj/project.pbxproj create mode 100644 pop.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 pop.xcodeproj/xcshareddata/xcschemes/pop-osx.xcscheme create mode 100644 pop.xcodeproj/xcshareddata/xcschemes/pop.xcscheme create mode 100644 pop.xcworkspace/contents.xcworkspacedata create mode 100644 pop/POP.h create mode 100644 pop/POPAction.h create mode 100644 pop/POPAnimatableProperty.h create mode 100644 pop/POPAnimatableProperty.mm create mode 100644 pop/POPAnimation.h create mode 100644 pop/POPAnimation.mm create mode 100644 pop/POPAnimationEvent.h create mode 100644 pop/POPAnimationEvent.mm create mode 100644 pop/POPAnimationEventInternal.h create mode 100644 pop/POPAnimationExtras.h create mode 100644 pop/POPAnimationExtras.mm create mode 100644 pop/POPAnimationInternal.h create mode 100644 pop/POPAnimationPrivate.h create mode 100644 pop/POPAnimationRuntime.h create mode 100644 pop/POPAnimationRuntime.mm create mode 100644 pop/POPAnimationTracer.h create mode 100644 pop/POPAnimationTracer.mm create mode 100644 pop/POPAnimationTracerInternal.h create mode 100644 pop/POPAnimator.h create mode 100644 pop/POPAnimator.mm create mode 100644 pop/POPAnimatorPrivate.h create mode 100644 pop/POPBasicAnimation.h create mode 100644 pop/POPBasicAnimation.mm create mode 100644 pop/POPBasicAnimationInternal.h create mode 100644 pop/POPCGUtils.h create mode 100644 pop/POPCGUtils.mm create mode 100644 pop/POPCustomAnimation.h create mode 100644 pop/POPCustomAnimation.mm create mode 100644 pop/POPDecayAnimation.h create mode 100644 pop/POPDecayAnimation.mm create mode 100644 pop/POPDecayAnimationInternal.h create mode 100644 pop/POPDefines.h create mode 100644 pop/POPGeometry.h create mode 100644 pop/POPGeometry.mm create mode 100644 pop/POPLayerExtras.h create mode 100644 pop/POPLayerExtras.mm create mode 100644 pop/POPMath.h create mode 100644 pop/POPMath.mm create mode 100644 pop/POPPropertyAnimation.h create mode 100644 pop/POPPropertyAnimation.mm create mode 100644 pop/POPPropertyAnimationInternal.h create mode 100644 pop/POPSpringAnimation.h create mode 100644 pop/POPSpringAnimation.mm create mode 100644 pop/POPSpringAnimationInternal.h create mode 100644 pop/POPSpringSolver.h create mode 100644 pop/POPVector.h create mode 100644 pop/POPVector.mm create mode 100644 pop/WebCore/FloatConversion.h create mode 100644 pop/WebCore/TransformationMatrix.cpp create mode 100644 pop/WebCore/TransformationMatrix.h create mode 100644 pop/WebCore/UnitBezier.h create mode 100644 pop/en.lproj/InfoPlist.strings create mode 100644 pop/pop-Info.plist create mode 100644 pop/pop-Prefix.pch diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..48f3fc62 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +.DS_Store + +*.pbxuser +*.perspective +*.perspectivev3 + +*.mode1v3 +*.mode2v3 + +*.xcodeproj/xcuserdata/*.xcuserdatad + +*.xccheckout +*.xcuserdatad + +Pods diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..6439bc50 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,28 @@ +# Contributing +We want to make contributing to Pop as easy and transparent as +possible. If you run into problems, please open an issue. We also actively welcome pull requests. + +## Pull Requests +1. Fork the repo and create your branch from `master`. +2. If you've added code that should be tested, add tests +3. If you've changed APIs, update the documentation. +4. Ensure the test suite passes. +5. Make sure your code lints. +6. If you haven't already, complete the Contributor License Agreement ("CLA"). + +## Contributor License Agreement ("CLA") +In order to accept your pull request, we need you to submit a CLA. You only need +to do this once to work on any of Facebook's open source projects. + +Complete your CLA here: + +## Issues +We use GitHub issues to track public bugs. Please ensure your description is +clear and has sufficient instructions to be able to reproduce the issue. + +## Coding Style +* 2 spaces for indentation rather than tabs + +## License +By contributing to Pop you agree that your contributions will be licensed +under its BSD license. diff --git a/Configuration/Applications/Application-OSX.xcconfig b/Configuration/Applications/Application-OSX.xcconfig new file mode 100644 index 00000000..9b9bc91b --- /dev/null +++ b/Configuration/Applications/Application-OSX.xcconfig @@ -0,0 +1,20 @@ +// +// 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. +// + +#include "../Base-OSX.xcconfig" + +// Deployment +DSTROOT = /tmp/$(TARGET_NAME) +SKIP_INSTALL = YES + +// Linking +OTHER_LDFLAGS = -ObjC -all_load -lc++ + +// Packaging +WRAPPER_EXTENSION = app diff --git a/Configuration/Applications/Application-iOS.xcconfig b/Configuration/Applications/Application-iOS.xcconfig new file mode 100644 index 00000000..3214cd40 --- /dev/null +++ b/Configuration/Applications/Application-iOS.xcconfig @@ -0,0 +1,23 @@ +// +// 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. +// + +#include "../Base-iOS.xcconfig" + +// Code Signing +CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer + +// Deployment +DSTROOT = /tmp/$(TARGET_NAME) +SKIP_INSTALL = YES + +// Linking +OTHER_LDFLAGS = -ObjC -all_load -lc++ + +// Packaging +WRAPPER_EXTENSION = app diff --git a/Configuration/Base-OSX.xcconfig b/Configuration/Base-OSX.xcconfig new file mode 100644 index 00000000..4aa529af --- /dev/null +++ b/Configuration/Base-OSX.xcconfig @@ -0,0 +1,12 @@ +// +// 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. +// + +// Base xcconfig for OS X targets. +#include "Platform/OSX.xcconfig" +#include "Platform/Compiler.xcconfig" diff --git a/Configuration/Base-iOS.xcconfig b/Configuration/Base-iOS.xcconfig new file mode 100644 index 00000000..1fde7c64 --- /dev/null +++ b/Configuration/Base-iOS.xcconfig @@ -0,0 +1,12 @@ +// +// 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. +// + +// Base xcconfig for iOS targets. +#include "Platform/iOS.xcconfig" +#include "Platform/Compiler.xcconfig" \ No newline at end of file diff --git a/Configuration/Platform/Compiler.xcconfig b/Configuration/Platform/Compiler.xcconfig new file mode 100644 index 00000000..d87d7567 --- /dev/null +++ b/Configuration/Platform/Compiler.xcconfig @@ -0,0 +1,36 @@ +// +// 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. +// + +// Don't warn on implicit property synthesis +CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = NO + +// Treat warnings as errors +GCC_TREAT_WARNINGS_AS_ERRORS = YES +CLANG_WARN_CONSTANT_CONVERSION = YES +CLANG_WARN_ENUM_CONVERSION = YES +CLANG_WARN_INT_CONVERSION = YES +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_BOOL_CONVERSION = YES +CLANG_WARN_EMPTY_BODY = YES +GCC_WARN_UNINITIALIZED_AUTOS = YES +GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO +GCC_WARN_UNUSED_FUNCTION = YES + +// Silence Eigen warnings +GCC_WARN_SHADOW = NO + +// Enable C++11 support +CLANG_CXX_LANGUAGE_STANDARD = c++11 +CLANG_CXX_LIBRARY = libc++ + +// Enable ARC +CLANG_ENABLE_OBJC_ARC = YES + +// Allow #import'ing code generated headers from DERIVED_FILE_DIR. +HEADER_SEARCH_PATHS = $(inherited) $(DERIVED_FILE_DIR) \ No newline at end of file diff --git a/Configuration/Platform/OSX.xcconfig b/Configuration/Platform/OSX.xcconfig new file mode 100644 index 00000000..8a7d68be --- /dev/null +++ b/Configuration/Platform/OSX.xcconfig @@ -0,0 +1,18 @@ +// +// 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. +// + +// OS X Platform +SDKROOT = macosx +SUPPORTED_PLATFORMS = macosx + +// Standard Architectures +ARCHS = $(ARCHS_STANDARD) + +// Limit to OS X 64-bit (x86_64) +VALID_ARCHS = x86_64 \ No newline at end of file diff --git a/Configuration/Platform/iOS.xcconfig b/Configuration/Platform/iOS.xcconfig new file mode 100644 index 00000000..015e6437 --- /dev/null +++ b/Configuration/Platform/iOS.xcconfig @@ -0,0 +1,18 @@ +// +// 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. +// + +// iOS Platform +SDKROOT = iphoneos +IPHONEOS_DEPLOYMENT_TARGET = 6.0 + +// Standard Architectures, including 64-bit +ARCHS = $(ARCHS_STANDARD_INCLUDING_64_BIT) + +// Code Signing +CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer diff --git a/Configuration/Projects/Project-Debug.xcconfig b/Configuration/Projects/Project-Debug.xcconfig new file mode 100644 index 00000000..a94dfe9d --- /dev/null +++ b/Configuration/Projects/Project-Debug.xcconfig @@ -0,0 +1,25 @@ +// +// 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. +// + +#include "Project.xcconfig" + +// Code Generation +GCC_OPTIMIZATION_LEVEL = 0 + +// Enable assertions +ENABLE_NS_ASSERTIONS = YES + +// Avoid compression overhead +COMPRESS_PNG_FILES = NO + +// Deployment +COPY_PHASE_STRIP = NO + +// Default to dwarf +DEBUG_INFORMATION_FORMAT = dwarf diff --git a/Configuration/Projects/Project-GCOV.xcconfig b/Configuration/Projects/Project-GCOV.xcconfig new file mode 100644 index 00000000..9b64e786 --- /dev/null +++ b/Configuration/Projects/Project-GCOV.xcconfig @@ -0,0 +1,13 @@ +// +// 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. +// + +#include "Project-Debug.xcconfig" + +GCC_GENERATE_TEST_COVERAGE_FILES = YES +GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES \ No newline at end of file diff --git a/Configuration/Projects/Project-Profile.xcconfig b/Configuration/Projects/Project-Profile.xcconfig new file mode 100644 index 00000000..88646ae0 --- /dev/null +++ b/Configuration/Projects/Project-Profile.xcconfig @@ -0,0 +1,31 @@ +// +// 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. +// + +#include "Project.xcconfig" + +// Code Generation +GCC_OPTIMIZATION_LEVEL = s + +// Disable assertions +ENABLE_NS_ASSERTIONS = NO + +// Enable Xcode PNG compression +COMPRESS_PNG_FILES = YES + +// Deployment +COPY_PHASE_STRIP = NO + +// Preprocessing +GCC_PREPROCESSOR_DEFINITIONS = PROFILE=1 NS_BLOCK_ASSERTIONS NDEBUG + +// We need debug symbols for profiling +DEBUG_INFORMATION_FORMAT = dwarf-with-dsym + +// Not all libraries have a Profile version. Allow using the Release version if they don't. +LIBRARY_SEARCH_PATHS = $(BUILT_PRODUCTS_DIR) $(BUILD_DIR)/Release$(EFFECTIVE_PLATFORM_NAME) diff --git a/Configuration/Projects/Project-Release.xcconfig b/Configuration/Projects/Project-Release.xcconfig new file mode 100644 index 00000000..2ca5bfdf --- /dev/null +++ b/Configuration/Projects/Project-Release.xcconfig @@ -0,0 +1,25 @@ +// +// 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. +// + +#include "Project.xcconfig" + +// Code Generation +GCC_OPTIMIZATION_LEVEL = s + +// Disable assertions +ENABLE_NS_ASSERTIONS = NO + +// Enable Xcode PNG compression +COMPRESS_PNG_FILES = YES + +// Build Options +VALIDATE_PRODUCT = YES + +// Preprocessing +GCC_PREPROCESSOR_DEFINITIONS = PROFILE=1 NS_BLOCK_ASSERTIONS NDEBUG diff --git a/Configuration/Projects/Project.xcconfig b/Configuration/Projects/Project.xcconfig new file mode 100644 index 00000000..1a175af9 --- /dev/null +++ b/Configuration/Projects/Project.xcconfig @@ -0,0 +1,48 @@ +// +// 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. +// + +#include "../Base-iOS.xcconfig" + +SDKROOT = iphoneos + +// Build Options +GCC_VERSION = com.apple.compilers.llvm.clang.1_0 + +// Deployment +TARGETED_DEVICE_FAMILY = 1,2 // iPhone, iPad + +// Packaging +PRODUCT_NAME = $(TARGET_NAME) +INFOPLIST_FILE = $(TARGET_NAME)/$(TARGET_NAME)-Info.plist + +// Search Paths +ALWAYS_SEARCH_USER_PATHS = NO +HEADER_SEARCH_PATHS = $(SYMROOT)/Headers +FRAMEWORK_SEARCH_PATHS = $(inherited) $(BUILT_PRODUCTS_DIR) +LIBRARY_SEARCH_PATHS = $(BUILT_PRODUCTS_DIR) $(inherited) + +// Code Generation +GCC_DYNAMIC_NO_PIC = NO +GCC_INLINES_ARE_PRIVATE_EXTERN = YES +GCC_SYMBOLS_PRIVATE_EXTERN = NO + +// Language +GCC_C_LANGUAGE_STANDARD = gnu99 +CLANG_ENABLE_OBJC_ARC = NO +GCC_PRECOMPILE_PREFIX_HEADER = YES +GCC_PREFIX_HEADER = $(TARGET_NAME)/$(TARGET_NAME)-Prefix.pch + +// Warnings +GCC_WARN_ABOUT_RETURN_TYPE = YES +GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES +GCC_WARN_MISSING_PARENTHESES = YES +GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES +GCC_WARN_SHADOW = YES +GCC_WARN_UNUSED_VARIABLE = YES +CLANG_WARN_CXX0X_EXTENSIONS = NO diff --git a/Configuration/Static Libraries/StaticLibrary-OSX.xcconfig b/Configuration/Static Libraries/StaticLibrary-OSX.xcconfig new file mode 100644 index 00000000..88b6fbce --- /dev/null +++ b/Configuration/Static Libraries/StaticLibrary-OSX.xcconfig @@ -0,0 +1,27 @@ +// +// 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. +// + +#include "../Base-OSX.xcconfig" + +// Deployment +DSTROOT = /tmp/$(TARGET_NAME) +INSTALL_PATH = $(SYMROOT)/Headers +SKIP_INSTALL = YES + +// Packaging: Put headers in $(SYMROOT) instead of $(CONFIGURATION_BUILD_DIR) +// so header paths across projects don't depend on all projects having an +// identical configuration name. +// +// Note: PUBLIC_HEADERS_FOLDER_PATH is directly appended to $(CONFIGURATION_BUILD_DIR), +// so the path is relative to that. + +PUBLIC_HEADERS_FOLDER_PATH = ../Headers/$(TARGET_NAME) + +// declare inlines as extern +GCC_INLINES_ARE_PRIVATE_EXTERN = YES diff --git a/Configuration/Static Libraries/StaticLibrary-iOS.xcconfig b/Configuration/Static Libraries/StaticLibrary-iOS.xcconfig new file mode 100644 index 00000000..a414eb37 --- /dev/null +++ b/Configuration/Static Libraries/StaticLibrary-iOS.xcconfig @@ -0,0 +1,27 @@ +// +// 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. +// + +#include "../Base-iOS.xcconfig" + +// Deployment +DSTROOT = /tmp/$(TARGET_NAME) +INSTALL_PATH = $(SYMROOT)/Headers +SKIP_INSTALL = YES + +// Packaging: Put headers in $(SYMROOT) instead of $(CONFIGURATION_BUILD_DIR) +// so header paths across projects don't depend on all projects having an +// identical configuration name. +// +// Note: PUBLIC_HEADERS_FOLDER_PATH is directly appended to $(CONFIGURATION_BUILD_DIR), +// so the path is relative to that. + +PUBLIC_HEADERS_FOLDER_PATH = ../Headers/$(TARGET_NAME) + +// declare inlines as extern +GCC_INLINES_ARE_PRIVATE_EXTERN = YES diff --git a/Configuration/Tests/ApplicationTests.xcconfig b/Configuration/Tests/ApplicationTests.xcconfig new file mode 100644 index 00000000..06438d87 --- /dev/null +++ b/Configuration/Tests/ApplicationTests.xcconfig @@ -0,0 +1,16 @@ +// +// 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. +// + +#include "LogicTests.xcconfig" + +// Linking +BUNDLE_LOADER = $(BUILT_PRODUCTS_DIR)/$(PROJECT_NAME)TestHost.app/$(PROJECT_NAME)TestHost + +// Unit Testing +TEST_HOST = "$(BUNDLE_LOADER)" diff --git a/Configuration/Tests/LogicTests-OSX.xcconfig b/Configuration/Tests/LogicTests-OSX.xcconfig new file mode 100644 index 00000000..a1b1ba1b --- /dev/null +++ b/Configuration/Tests/LogicTests-OSX.xcconfig @@ -0,0 +1,23 @@ +// +// 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. +// + +#include "../Base-OSX.xcconfig" + +// Deployment +DSTROOT = /tmp/$(TARGET_NAME) +SKIP_INSTALL = YES + +// Linking +OTHER_LDFLAGS = -framework SenTestingKit -all_load -lc++ + +// Packaging +WRAPPER_EXTENSION = octest + +// Search Paths +FRAMEWORK_SEARCH_PATHS = $(DEVELOPER_FRAMEWORKS_DIR) diff --git a/Configuration/Tests/LogicTests-iOS.xcconfig b/Configuration/Tests/LogicTests-iOS.xcconfig new file mode 100644 index 00000000..2d2a95e6 --- /dev/null +++ b/Configuration/Tests/LogicTests-iOS.xcconfig @@ -0,0 +1,26 @@ +// +// 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. +// + +#include "../Base-iOS.xcconfig" + +// Code Signing +CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer + +// Deployment +DSTROOT = /tmp/$(TARGET_NAME) +SKIP_INSTALL = YES + +// Linking +OTHER_LDFLAGS = -framework SenTestingKit -all_load -lc++ + +// Packaging +WRAPPER_EXTENSION = octest + +// Search Paths +FRAMEWORK_SEARCH_PATHS = $(SDKROOT)/Developer/Library/Frameworks $(DEVELOPER_FRAMEWORKS_DIR) diff --git a/Images/pop.gif b/Images/pop.gif new file mode 100644 index 0000000000000000000000000000000000000000..b6ddecdbb1fa20ef471bb44cf9403d2bbee20b56 GIT binary patch literal 182963 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 V1=-)D>4L1K@0#u|10IN5{|^{fClCMt literal 0 HcmV?d00001 diff --git a/Images/pop.png b/Images/pop.png new file mode 100644 index 0000000000000000000000000000000000000000..f25bbaabf2f5c7bc857608befc41b45cbaf308e3 GIT binary patch literal 9643 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 literal 0 HcmV?d00001 diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..642126fd --- /dev/null +++ b/LICENSE @@ -0,0 +1,30 @@ +BSD License + +For Pop software + +Copyright (c) 2014, Facebook, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/PATENTS b/PATENTS new file mode 100644 index 00000000..7920535f --- /dev/null +++ b/PATENTS @@ -0,0 +1,23 @@ +Additional Grant of Patent Rights + +"Software" means the Pop software distributed by Facebook, Inc. + +Facebook hereby grants you a perpetual, worldwide, royalty-free, non-exclusive, +irrevocable (subject to the termination provision below) license under any +rights in any patent claims owned by Facebook, to make, have made, use, sell, +offer to sell, import, and otherwise transfer the Software. For avoidance of +doubt, no license is granted under Facebook’s rights in any patent claims that +are infringed by (i) modifications to the Software made by you or a third party, +or (ii) the Software in combination with any software or other technology +provided by you or a third party. + +The license granted hereunder will terminate, automatically and without notice, +for anyone that makes any claim (including by filing any lawsuit, assertion or +other action) alleging (a) direct, indirect, or contributory infringement or +inducement to infringe any patent: (i) by Facebook or any of its subsidiaries or +affiliates, whether or not such claim is related to the Software, (ii) by any +party if such claim arises in whole or in part from any software, product or +service of Facebook or any of its subsidiaries or affiliates, whether or not +such claim is related to the Software, or (iii) by any party relating to the +Software; or (b) that any right in any patent claim of Facebook is invalid or +unenforceable. diff --git a/Podfile b/Podfile new file mode 100755 index 00000000..a1f2b93a --- /dev/null +++ b/Podfile @@ -0,0 +1,10 @@ +platform :ios, '7.0' + +target :'pop-tests', :exclusive => true do + pod 'OCMock', '~> 2.2' +end + +target :'pop-tests-osx', :exclusive => true do + platform :osx, '10.9' + pod 'OCMock', '~> 2.2' +end \ No newline at end of file diff --git a/Podfile.lock b/Podfile.lock new file mode 100644 index 00000000..5a3394c7 --- /dev/null +++ b/Podfile.lock @@ -0,0 +1,10 @@ +PODS: + - OCMock (2.2.3) + +DEPENDENCIES: + - OCMock (~> 2.2) + +SPEC CHECKSUMS: + OCMock: ea7fe30ee99e4e3186ad47d032a301fa7e8793ec + +COCOAPODS: 0.32.1 diff --git a/README.md b/README.md new file mode 100644 index 00000000..57897b5a --- /dev/null +++ b/README.md @@ -0,0 +1,149 @@ +![pop](https://github.com/facebook/pop/blob/master/Images/pop.gif?raw=true) + +Pop is an extensible animation engine for iOS and OS X. In addition to basic static animations, it supports spring and decay dynamic animations, making it useful for building realistic, physics-based interactions. The API allows quick integration with existing Objective-C codebases and enables the animation of any property on any object. It's a mature and well-tested framework that drives all the animations and transitions in [Paper](http://www.facebook.com/paper). + +## Installation + +Pop will soon be available on [CocoaPods](http://cocoapods.org). Once indexed, add the following to your project Podfile: + +```ruby +pod 'pop' +``` +Alternatively, you can add the project to your workspace and adopt the provided configuration files or manually copy the files under the pop subdirectory into your project. + +## Usage + +Pop adopts the Core Animation explicit animation programming model. Use by including the following import: + +```objective-c +#import +``` + +### Start, Stop & Update + +To start an animation, add it to the object you wish to animate: + +```objective-c +POPSpringAnimation *anim = [POPSpringAnimation animation]; +... +[layer pop_addAnimation:anim forKey:@"myKey"]; +``` + +To stop an animation, remove it from the object referencing the key specified on start: + +```objective-c +[layer pop_removeAnimationForKey:@"myKey"]; +``` + +The key can also be used to query for the existence of an animation. Updating the toValue of a running animation can provide the most seamless way to change course: + +```objective-c +anim = [layer pop_animationForKey:@"myKey"]; +if (anim) { + /* update to value to new destination */ + anim.toValue = @(42.0); +} else { + /* create and start a new animation */ + .... +} +``` + +While a layer was used in the above examples, the Pop interface is implemented as a category addition on NSObject. Any NSObject or subclass can be animated. + +### Types + +There are four concrete animation types: spring, decay, basic and custom. + +Spring animations can be used to give objects a delightful bounce. In this example, we using a spring animation to animates a layer's bounds from its current value to (0, 0, 400, 400): + +```objective-c +POPSpringAnimation *anim = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerBounds]; +anim.toValue = [NSValue valueWithCGRect:CGRectMake(0, 0, 400, 400)]; +[layer pop_addAnimation:anim forKey:@"size"]; +``` +Decay animations can be used to gradually slow an object to a halt. In this example, we decay a layer's positionX from it's current value and velocity 1000pts per second: + +```objective-c +POPDecayAnimation *anim = [POPDecayAnimation animationWithPropertyNamed:kPOPLayerPositionX]; +anim.velocity = @(1000.); +[layer pop_addAnimation:anim forKey:@"slide"]; +``` + +Basic animations can be used to interpolate values over a specified time period. To use an ease-in ease-out animation to animate a view's alpha from 0.0 to 1.0 over the default duration: +```objective-c +POPBasicAnimation *anim = [POPBasicAnimation animationWithPropertyNamed:kPOPViewAlpha]; +anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; +anim.fromValue = @(0.0); +anim.toValue = @(1.0); +[view pop_addAnimation:anim forKey:@"slide"]; +``` +`POPCustomAnimation` makes creating custom animations and transitions easier by handling CADisplayLink and associated time-step management. See header for more details. + + +### Properties + +The property animated is specified by the `POPAnimatableProperty` class. In this example we create a spring animation and explicitly set the animatable property corresponding to `-[CALayer bounds]`: + +```objective-c +POPSpringAnimation *anim = [POPSpringAnimation animation]; +anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerBounds]; +``` + +The framework provides many common layer and view animatable properties out of box. You can animate a custom property by creating a new instance of the class. In this example, we declare a custom volume property: + +```objective-c +prop = [POPAnimatableProperty propertyWithName:@"com.foo.radio.volume" initializer:^(POPMutableAnimatableProperty *prop) { + // read value + prop.readBlock = ^(id obj, CGFloat values[]) { + values[0] = [obj volume]; + }; + // write value + prop.writeBlock = ^(id obj, const CGFloat values[]) { + [obj setVolume:values[0]]; + }; + // dynamics threshold + prop.threshold = 0.01; +}]; + +anim.property = prop; +``` + +For a complete listing of provided animatable properties, as well more information on declaring custom properties see `POPAnimatableProperty.h`. + + +### Debugging + +Here are a few tips when debugging. Pop obeys the Simulator's Toggle Slow Animations setting. Try enabling it to slow down animations and more easily observe interactions. + +Consider naming your animations. This will allow you to more easily identify them when referencing them, either via logging or in the debugger: + +```objective-c +anim.name = @"springOpen"; +``` + +Each animation comes with an associated tracer. The tracer allows you to record all animation-related events, in a fast and efficient manner, allowing you to query and analyze them after animation completion. The below example starts the tracer and configures it to log all events on animation completion: + +```objective-c +POPAnimationTracer *tracer = anim.tracer; +tracer.shouldLogAndResetOnCompletion = YES; +[tracer start]; +``` + +See `POPAnimationTracer.h` for more details. + +## Testing + +Pop has extensive unit test coverage. To install test dependencies, navigate to the root pop directory and type: + +```sh +pod install +``` + +Assuming CocoaPods is installed, this will include the necessary OCMock dependency to the unit test targets. + +## Contributing +See the CONTRIBUTING file for how to help out. + +## Licence + +Pop is released under a BSD License. See LICENSE file for details. diff --git a/pop-tests-osx/en.lproj/InfoPlist.strings b/pop-tests-osx/en.lproj/InfoPlist.strings new file mode 100644 index 00000000..477b28ff --- /dev/null +++ b/pop-tests-osx/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/pop-tests-osx/pop-tests-OSX-Info.plist b/pop-tests-osx/pop-tests-OSX-Info.plist new file mode 100644 index 00000000..c317ef52 --- /dev/null +++ b/pop-tests-osx/pop-tests-OSX-Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.facebook.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/pop-tests-osx/pop-tests-OSX-Prefix.pch b/pop-tests-osx/pop-tests-OSX-Prefix.pch new file mode 100644 index 00000000..4187f19f --- /dev/null +++ b/pop-tests-osx/pop-tests-OSX-Prefix.pch @@ -0,0 +1,9 @@ +// +// Prefix header +// +// The contents of this file are implicitly included at the beginning of every source file. +// + +#ifdef __OBJC__ + #import +#endif diff --git a/pop-tests/POPAnimatable.h b/pop-tests/POPAnimatable.h new file mode 100644 index 00000000..3783fae4 --- /dev/null +++ b/pop-tests/POPAnimatable.h @@ -0,0 +1,26 @@ +/** + 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 +#import +#import + +@interface POPAnimatable : NSObject + +@property (nonatomic, assign) float radius; + +@property (nonatomic, assign) CGPoint position; + +- (NSArray *)recordedValuesForKey:(NSString *)key; + +- (void)startRecording; + +- (void)stopRecording; + +@end diff --git a/pop-tests/POPAnimatable.mm b/pop-tests/POPAnimatable.mm new file mode 100644 index 00000000..00c10887 --- /dev/null +++ b/pop-tests/POPAnimatable.mm @@ -0,0 +1,74 @@ +/** + 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 "POPAnimatable.h" + +#import + +@implementation POPAnimatable +{ + BOOL _recording; + NSMutableDictionary *_recordedValuesDict; +} + +static void record_value(POPAnimatable *self, NSString *key, id value) +{ + if (!self->_recordedValuesDict) { + self->_recordedValuesDict = [NSMutableDictionary new]; + } + NSMutableArray *values = self->_recordedValuesDict[key]; + if (!values) { + values = [NSMutableArray array]; + self->_recordedValuesDict[key] = values; + } + [values addObject:value]; +} + +static void record_value(POPAnimatable *self, NSString *key, float f) +{ + record_value(self, key, @(f)); +} + +static void record_value(POPAnimatable *self, NSString *key, CGPoint p) +{ + record_value(self, key, [NSValue valueWithCGPoint:p]); +} + +- (void)setRadius:(float)radius +{ + _radius = radius; + if (_recording) { + record_value(self, @"radius", radius); + } +} + +- (void)setPosition:(CGPoint)position +{ + _position = position; + if (_recording) { + record_value(self, @"position", position); + } +} + +- (NSArray *)recordedValuesForKey:(NSString *)key +{ + return _recordedValuesDict[key]; +} + +- (void)startRecording +{ + _recording = YES; +} + +- (void)stopRecording +{ + _recording = NO; +} + +@end diff --git a/pop-tests/POPAnimatablePropertyTests.mm b/pop-tests/POPAnimatablePropertyTests.mm new file mode 100644 index 00000000..ac0dd576 --- /dev/null +++ b/pop-tests/POPAnimatablePropertyTests.mm @@ -0,0 +1,115 @@ +/** + 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 + +#import + +static const CGFloat epsilon = 0.0001f; +static NSArray *properties = @[@"name", @"readBlock", @"writeBlock", @"threshold"]; + +static void assertPropertyEqual(id self, POPAnimatableProperty *prop1, POPAnimatableProperty *prop2) +{ + for (NSString *property in properties) { + id value = [prop1 valueForKey:property]; + id valueCopy = [prop2 valueForKey:property]; + STAssertEqualObjects(value, valueCopy, @"unexpected inequality; value:%@ copy:%@", value, valueCopy); + } +} + +@interface POPAnimatablePropertyTests : SenTestCase +@end + +@implementation POPAnimatablePropertyTests + +- (void)testProvidedExistence +{ + NSArray *names = @[kPOPLayerPosition, + kPOPLayerOpacity, + kPOPLayerScaleXY, + kPOPLayerSubscaleXY, + kPOPLayerSubtranslationX, + kPOPLayerSubtranslationY, + kPOPLayerSubtranslationZ, + kPOPLayerSubtranslationXY, + kPOPLayerZPosition, + kPOPLayerSize, + kPOPLayerRotation, + kPOPLayerRotationY, + kPOPLayerRotationX, +#if TARGET_OS_IPHONE + kPOPViewAlpha, + kPOPViewBackgroundColor, + kPOPViewCenter, + kPOPViewFrame, + kPOPViewBounds, + kPOPViewSize, + kPOPTableViewContentSize, + kPOPTableViewContentOffset +#endif + ]; + + for (NSString *name in names) { + POPAnimatableProperty *prop = [POPAnimatableProperty propertyWithName:name]; + STAssertNotNil(prop, @"animatable property %@ should exist", name); + } +} + +- (void)testUserCreation +{ + static NSString *name = @"lalalala"; + static CGFloat threshold = 0.07; + POPAnimatableProperty *prop; + + prop = [POPAnimatableProperty propertyWithName:name]; + STAssertNil(prop, @"animatable property %@ should not exist", name); + + prop = [POPAnimatableProperty propertyWithName:name initializer:^(POPMutableAnimatableProperty *p){ + p.threshold = threshold; + }]; + STAssertNotNil(prop, @"animatable property %@ should exist", name); + STAssertEqualsWithAccuracy(threshold, prop.threshold, epsilon, @"property threshold %f should equal %f", prop.threshold, threshold); +} + +- (void)testClassCluster +{ + POPAnimatableProperty *instance1 = [[POPAnimatableProperty alloc] init]; + POPAnimatableProperty *instance2 = [[POPAnimatableProperty alloc] init]; + STAssertTrue(instance1 == instance2, @"instance1:%@ instance2:%@", instance1, instance2); + + for (NSString *property in properties) { + STAssertNoThrow([instance1 valueForKey:property], @"exception on %@", property); + } +} + +- (void)testCopying +{ + // instance + POPAnimatableProperty *prop = [POPAnimatableProperty propertyWithName:kPOPLayerBounds]; + + // instance copy + POPAnimatableProperty *propCopy = [prop copy]; + + // test equality + assertPropertyEqual(self, prop, propCopy); +} + +- (void)testMutableCopying +{ + // instance + POPAnimatableProperty *prop = [POPAnimatableProperty propertyWithName:kPOPLayerBounds]; + + // instance copy + POPAnimatableProperty *propCopy = [prop mutableCopy]; + + // test equality + assertPropertyEqual(self, prop, propCopy); +} + +@end diff --git a/pop-tests/POPAnimationMRRTests.mm b/pop-tests/POPAnimationMRRTests.mm new file mode 100644 index 00000000..405700ed --- /dev/null +++ b/pop-tests/POPAnimationMRRTests.mm @@ -0,0 +1,88 @@ +/** + 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 + +#import +#import + +#import + +#import "POPAnimationTestsExtras.h" + +@interface POPAnimationMRRTests : SenTestCase +{ + POPAnimator *_animator; + CFTimeInterval _beginTime; +} +@end + +@implementation POPAnimationMRRTests + +- (void)setUp +{ + [super setUp]; + _animator = [[POPAnimator sharedAnimator] retain]; + _beginTime = CACurrentMediaTime(); +} + +- (void)tearDown +{ + [_animator release]; + _animator = nil; + [super tearDown]; +} + +- (void)testZeroingDelegate +{ + POPBasicAnimation *anim = FBTestLinearPositionAnimation(); + + @autoreleasepool { + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + anim.delegate = delegate; + STAssertNotNil(anim.delegate, @"delegate should not be nil"); + } + + STAssertNil(anim.delegate, @"delegate should be nil"); +} + +- (void)testAnimationCancellationOnAnimatableDeallocation +{ + id layer = nil; + POPBasicAnimation *anim = FBTestLinearPositionAnimation(); + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + + @autoreleasepool { + layer = [OCMockObject niceMockForClass:[CALayer class]]; + anim.delegate = delegate; + + // expect position start + [[delegate expect] pop_animationDidStart:anim]; + + // run + [layer pop_addAnimation:anim forKey:@""]; + POPAnimatorRenderTimes(_animator, _beginTime, @[@0.0]); + + // verify + [layer verify]; + [delegate verify]; + + // expect stop unfinished + [[delegate expect] pop_animationDidStop:anim finished:NO]; + layer = nil; + } + + // run + POPAnimatorRenderTimes(_animator, _beginTime, @[@0.5]); + + // verify + [delegate verify]; +} + +@end diff --git a/pop-tests/POPAnimationTests.mm b/pop-tests/POPAnimationTests.mm new file mode 100644 index 00000000..5a928df1 --- /dev/null +++ b/pop-tests/POPAnimationTests.mm @@ -0,0 +1,616 @@ +/** + 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 + +#import +#import + +#import +#import +#import + +#import "POPAnimatable.h" +#import "POPAnimationRuntime.h" +#import "POPAnimationTestsExtras.h" +#import "POPBaseAnimationTests.h" + +using namespace POP; + +@interface POPAnimation (TestExtensions) +@property (strong, nonatomic) NSString *sampleKey; +@end + +@implementation POPAnimation (TestExtensions) +- (NSString *)sampleKey { return [self valueForUndefinedKey:@"sampleKey"]; } +- (void)setSampleKey:(NSString *)aValue { [self setValue:aValue forUndefinedKey:@"sampleKey"];} +@end + +@interface POPAnimationTests : POPBaseAnimationTests +@end + +@implementation POPAnimationTests + +- (void)testOrneryAbstractClasses +{ + STAssertThrows([[POPAnimation alloc] init], @"should not be able to instiate abstract class"); + STAssertThrows([[POPPropertyAnimation alloc] init], @"should not be able to instiate abstract class"); +} + +- (void)testWithPropertyNamedConstruction +{ + POPSpringAnimation *anim = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerBounds]; + POPAnimatableProperty *prop = [POPAnimatableProperty propertyWithName:kPOPLayerBounds]; + STAssertTrue(anim.property == prop, @"expected:%@ actual:%@", prop, anim.property); +} + +- (void)testAdditionRemoval +{ + CALayer *layer1 = self.layer1; + CALayer *layer2 = self.layer2; + [layer1 removeAllAnimations]; + [layer2 removeAllAnimations]; + + POPAnimation *anim = FBTestLinearPositionAnimation(self.beginTime); + [layer1 pop_addAnimation:anim forKey:@"hello"]; + + NSArray *keys = [layer1 pop_animationKeys]; + STAssertTrue(1 == keys.count, nil); + STAssertTrue([@"hello" isEqualToString:keys.lastObject], nil); + + [layer1 pop_removeAnimationForKey:@"hello"]; + STAssertTrue(0 == [layer1 pop_animationKeys].count, nil); + + [layer1 pop_addAnimation:FBTestLinearPositionAnimation(self.beginTime) forKey:@"hello"]; + [layer1 pop_addAnimation:FBTestLinearPositionAnimation(self.beginTime) forKey:@"world"]; + [layer2 pop_addAnimation:FBTestLinearPositionAnimation(self.beginTime) forKey:@"hello"]; + + STAssertTrue(2 == [layer1 pop_animationKeys].count, nil); + STAssertTrue(1 == [layer2 pop_animationKeys].count, nil); + + [layer1 pop_removeAllAnimations]; + STAssertTrue(0 == [layer1 pop_animationKeys].count, nil); + STAssertTrue(1 == [layer2 pop_animationKeys].count, nil); +} + +- (void)testStartStopDelegation +{ + CALayer *layer1 = self.layer1; + [layer1 removeAllAnimations]; + + POPAnimation *anim = FBTestLinearPositionAnimation(self.beginTime); + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + + // expect start, stop finished + [[delegate expect] pop_animationDidStart:anim]; + [[delegate expect] pop_animationDidStop:anim finished:YES]; + anim.delegate = delegate; + + [layer1 pop_addAnimation:anim forKey:@"key"]; + POPAnimatorRenderTimes(self.animator, self.beginTime, @[@0.0, @1.0]); + + // verify expectations + [delegate verify]; +} + +- (void)testAnimationValues +{ + POPBasicAnimation *anim = FBTestLinearPositionAnimation(self.beginTime); + + // avoid fractional values; simplify verification + anim.roundingFactor = 1.0; + + id layer = [OCMockObject niceMockForClass:[CALayer class]]; + [[layer expect] setPosition:FBTestInterpolateLinear(Vector2r([anim.fromValue CGPointValue]), Vector2r([anim.toValue CGPointValue]), 0.25).cg_point()]; + [[layer expect] setPosition:FBTestInterpolateLinear(Vector2r([anim.fromValue CGPointValue]), Vector2r([anim.toValue CGPointValue]), 0.5).cg_point()]; + [[layer expect] setPosition:FBTestInterpolateLinear(Vector2r([anim.fromValue CGPointValue]), Vector2r([anim.toValue CGPointValue]), 0.75).cg_point()]; + [[layer expect] setPosition:[anim.toValue CGPointValue]]; + + [layer pop_addAnimation:anim forKey:@"key"]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 1, 0.25); + + [layer verify]; +} + +- (void)testReAddition +{ + CALayer *layer1 = self.layer1; + CALayer *layer2 = self.layer2; + [layer1 removeAllAnimations]; + [layer2 removeAllAnimations]; + + static NSString *key = @"key"; + + POPAnimation *anim1, *anim2; + id delegate1, delegate2; + + anim1 = FBTestLinearPositionAnimation(self.beginTime); + delegate1 = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + + // expect start, stop not finished + [[delegate1 expect] pop_animationDidStart:anim1]; + [[delegate1 expect] pop_animationDidStop:anim1 finished:NO]; + + anim1.delegate = delegate1; + [layer1 pop_addAnimation:anim1 forKey:key]; + + anim2 = FBTestLinearPositionAnimation(self.beginTime); + delegate2 = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + + // expect start, stop finished + [[delegate2 expect] pop_animationDidStart:anim2]; + [[delegate2 expect] pop_animationDidStop:anim2 finished:YES]; + anim2.delegate = delegate2; + + // add with same key + [layer1 pop_addAnimation:anim2 forKey:key]; + POPAnimatorRenderTimes(self.animator, self.beginTime, @[@0.0, @1.0]); + + // verify expectations + [delegate1 verify]; + [delegate2 verify]; +} + +- (void)testCompletionBlock +{ + CALayer *layer1 = self.layer1; + [layer1 removeAllAnimations]; + + POPAnimation *anim = FBTestLinearPositionAnimation(self.beginTime); + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + + anim.completionBlock = ^(POPAnimation *a, BOOL finished) { + [delegate pop_animationDidStop:a finished:finished]; + }; + + // test for unfinished completion + [[delegate expect] pop_animationDidStop:anim finished:NO]; + + [layer1 pop_addAnimation:anim forKey:@"key"]; + [layer1 pop_removeAllAnimations]; + [delegate verify]; + + anim = FBTestLinearPositionAnimation(self.beginTime); + delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + + // set completion block + anim.completionBlock = ^(POPAnimation *a, BOOL finished) { + [delegate pop_animationDidStop:a finished:finished]; + }; + + // test for finished completion + [[delegate expect] pop_animationDidStop:anim finished:YES]; + + [layer1 pop_addAnimation:anim forKey:@"key"]; + POPAnimatorRenderTimes(self.animator, self.beginTime, @[@0.0, @1.0]); + [delegate verify]; +} + +- (void)testReuse +{ + NSValue *fromValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)]; + NSValue *toValue = [NSValue valueWithCGPoint:CGPointMake(200, 200)]; + CGFloat testProgress = 0.25; + + POPBasicAnimation *anim = FBTestLinearPositionAnimation(self.beginTime); + anim.fromValue = fromValue; + anim.toValue = toValue; + anim.roundingFactor = 1.0; + + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + [[delegate expect] pop_animationDidStart:anim]; + [[delegate expect] pop_animationDidStop:anim finished:YES]; + anim.delegate = delegate; + + id layer = [OCMockObject niceMockForClass:[CALayer class]]; + + [[layer expect] setPosition:FBTestInterpolateLinear(Vector2r([fromValue CGPointValue]), Vector2r([toValue CGPointValue]), testProgress).cg_point()]; + [[layer expect] setPosition:[toValue CGPointValue]]; + + [layer pop_addAnimation:anim forKey:@"key"]; + + POPAnimatorRenderTimes(self.animator, self.beginTime, @[@0.0, [NSNumber numberWithFloat:testProgress * anim.duration], [NSNumber numberWithFloat:anim.duration]]); + [layer verify]; + [delegate verify]; + + // new delegate & layer, same animation + delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + [[delegate expect] pop_animationDidStart:anim]; + [[delegate expect] pop_animationDidStop:anim finished:YES]; + anim.delegate = delegate; + + layer = [OCMockObject niceMockForClass:[CALayer class]]; + + [[layer expect] setPosition:FBTestInterpolateLinear(Vector2r([fromValue CGPointValue]), Vector2r([toValue CGPointValue]), testProgress).cg_point()]; + [[layer expect] setPosition:[toValue CGPointValue]]; + + [layer pop_addAnimation:anim forKey:@"key"]; + + POPAnimatorRenderTimes(self.animator, self.beginTime, @[@0.0, [NSNumber numberWithFloat:testProgress * anim.duration], [NSNumber numberWithFloat:anim.duration]]); + [layer verify]; + + [delegate verify]; +} + +- (void)testAddedKeys +{ + POPAnimation *anim = FBTestLinearPositionAnimation(); + anim.sampleKey = @"value"; + STAssertEqualObjects(anim.sampleKey, @"value", @"property value read should equal write"); +} + +- (void)testValueTypeResolution +{ + POPSpringAnimation *anim = [POPSpringAnimation animation]; + STAssertNil(anim.fromValue, nil); + STAssertNil(anim.toValue, nil); + STAssertNil(anim.velocity, nil); + + id pointValue = [NSValue valueWithCGPoint:CGPointMake(1, 2)]; + anim.fromValue = pointValue; + anim.toValue = pointValue; + anim.velocity = pointValue; + + STAssertEqualObjects(anim.fromValue, pointValue, @"property value read should equal write"); + STAssertEqualObjects(anim.toValue, pointValue, @"property value read should equal write"); + STAssertEqualObjects(anim.velocity, pointValue, @"property value read should equal write"); + + POPSpringAnimation *anim2 = [POPSpringAnimation animation]; + + id rectValue = [NSValue valueWithCGRect:CGRectMake(0, 0, 20, 20)]; + anim2.fromValue = rectValue; + anim2.toValue = rectValue; + anim2.velocity = rectValue; + + STAssertEqualObjects(anim2.fromValue, rectValue, @"property value read should equal write"); + STAssertEqualObjects(anim2.toValue, rectValue, @"property value read should equal write"); + STAssertEqualObjects(anim2.velocity, rectValue, @"property value read should equal write"); + + POPSpringAnimation *anim3 = [POPSpringAnimation animation]; + id transformValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; + STAssertThrows(anim3.fromValue = transformValue, @"should not be able to set %@", transformValue); +} + +- (void)testTracer +{ + POPAnimatable *circle = [POPAnimatable new]; + POPSpringAnimation *anim = [POPSpringAnimation animation]; + POPAnimationTracer *tracer = anim.tracer; + STAssertNotNil(tracer, @"missing tracer"); + [tracer start]; + + NSNumber *animFromValue = @0.0; + NSNumber *animToValue = @1.0; + NSNumber *animVelocity = @0.1; + float animBounciness = 4.1; + float animSpeed = 13.0; + float animFriction = 123.; + float animMass = 0.9; + float animTension = 401.; + + anim.property = self.radiusProperty; + anim.fromValue = animFromValue; + anim.toValue = animToValue; + anim.velocity = animVelocity; + anim.dynamicsFriction = animFriction; + anim.dynamicsMass = animMass; + anim.dynamicsTension = animTension; + anim.springBounciness = animBounciness; + anim.springSpeed = animSpeed; + + [circle pop_addAnimation:anim forKey:@"key"]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 5, 0.01); + [tracer stop]; + + NSArray *allEvents = tracer.allEvents; + NSArray *fromEvents = [tracer eventsWithType:kPOPAnimationEventFromValueUpdate]; + NSArray *toEvents = [tracer eventsWithType:kPOPAnimationEventToValueUpdate]; + NSArray *velocityEvents = [tracer eventsWithType:kPOPAnimationEventVelocityUpdate]; + NSArray *bouncinessEvents = [tracer eventsWithType:kPOPAnimationEventBouncinessUpdate]; + NSArray *speedEvents = [tracer eventsWithType:kPOPAnimationEventSpeedUpdate]; + NSArray *frictionEvents = [tracer eventsWithType:kPOPAnimationEventFrictionUpdate]; + NSArray *massEvents = [tracer eventsWithType:kPOPAnimationEventMassUpdate]; + NSArray *tensionEvents = [tracer eventsWithType:kPOPAnimationEventTensionUpdate]; + NSArray *startEvents = [tracer eventsWithType:kPOPAnimationEventDidStart]; + NSArray *stopEvents = [tracer eventsWithType:kPOPAnimationEventDidStop]; + NSArray *didReachEvents = [tracer eventsWithType:kPOPAnimationEventDidReachToValue]; + NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite]; + + // all events + STAssertTrue(0 != allEvents.count, @"unexpected allEvents count %@", allEvents); + + // from events + STAssertTrue(1 == fromEvents.count, @"unexpected fromEvents count %@", fromEvents); + id eventFromValue = [(POPAnimationValueEvent *)fromEvents.lastObject value]; + STAssertEqualObjects(animFromValue, eventFromValue, @"unexpected eventFromValue; expected:%@ actual:%@", animFromValue, eventFromValue); + + // to events + STAssertTrue(1 == toEvents.count, @"unexpected toEvents count %@", toEvents); + id eventToValue = [(POPAnimationValueEvent *)toEvents.lastObject value]; + STAssertEqualObjects(animToValue, eventToValue, @"unexpected eventToValue; expected:%@ actual:%@", animToValue, eventToValue); + + // velocity events + STAssertTrue(1 == velocityEvents.count, @"unexpected velocityEvents count %@", velocityEvents); + id eventVelocity = [(POPAnimationValueEvent *)velocityEvents.lastObject value]; + STAssertEqualObjects(animVelocity, eventVelocity, @"unexpected eventVelocity; expected:%@ actual:%@", animVelocity, eventVelocity); + + // bounciness events + STAssertTrue(1 == bouncinessEvents.count, @"unexpected bouncinessEvents count %@", bouncinessEvents); + id eventBounciness = [(POPAnimationValueEvent *)bouncinessEvents.lastObject value]; + STAssertEqualObjects(@(animBounciness), eventBounciness, @"unexpected bounciness; expected:%@ actual:%@", @(animBounciness), eventBounciness); + + // speed events + STAssertTrue(1 == speedEvents.count, @"unexpected speedEvents count %@", speedEvents); + id eventSpeed = [(POPAnimationValueEvent *)speedEvents.lastObject value]; + STAssertEqualObjects(@(animSpeed), eventSpeed, @"unexpected speed; expected:%@ actual:%@", @(animSpeed), eventSpeed); + + // friction events + STAssertTrue(1 == frictionEvents.count, @"unexpected frictionEvents count %@", frictionEvents); + id eventFriction = [(POPAnimationValueEvent *)frictionEvents.lastObject value]; + STAssertEqualObjects(@(animFriction), eventFriction, @"unexpected friction; expected:%@ actual:%@", @(animFriction), eventFriction); + + // mass events + STAssertTrue(1 == massEvents.count, @"unexpected massEvents count %@", massEvents); + id eventMass = [(POPAnimationValueEvent *)massEvents.lastObject value]; + STAssertEqualObjects(@(animMass), eventMass, @"unexpected mass; expected:%@ actual:%@", @(animMass), eventMass); + + // tension events + STAssertTrue(1 == tensionEvents.count, @"unexpected tensionEvents count %@", tensionEvents); + id eventTension = [(POPAnimationValueEvent *)tensionEvents.lastObject value]; + STAssertEqualObjects(@(animTension), eventTension, @"unexpected tension; expected:%@ actual:%@", @(animTension), eventTension); + + // start & stop event + STAssertTrue(1 == startEvents.count, @"unexpected startEvents count %@", startEvents); + STAssertTrue(1 == stopEvents.count, @"unexpected stopEvents count %@", stopEvents); + + // start before stop + NSUInteger startIdx = [allEvents indexOfObjectIdenticalTo:startEvents.firstObject]; + NSUInteger stopIdx = [allEvents indexOfObjectIdenticalTo:stopEvents.firstObject]; + STAssertTrue(startIdx < stopIdx, @"unexpected start/stop ordering startIdx:%d stopIdx:%d", startIdx, stopIdx); + + // did reach event + STAssertTrue(1 == didReachEvents.count, @"unexpected didReachEvents %@", didReachEvents); + + // did reach after start before stop + NSUInteger didReachIdx = [allEvents indexOfObjectIdenticalTo:didReachEvents.firstObject]; + STAssertTrue(didReachIdx > startIdx, @"unexpected didReach/start ordering didReachIdx:%d startIdx:%d", didReachIdx, startIdx); + STAssertTrue(didReachIdx < stopIdx, @"unexpected didReach/stop ordering didReachIdx:%d stopIdx:%d", didReachIdx, stopIdx); + + // write events + STAssertTrue(0 != writeEvents.count, @"unexpected writeEvents count %@", writeEvents); + id firstWriteValue = [(POPAnimationValueEvent *)writeEvents.firstObject value]; + STAssertTrue(NSOrderedAscending == [anim.fromValue compare:firstWriteValue], @"unexpected firstWriteValue; fromValue:%@ actual:%@", anim.fromValue, firstWriteValue); + id lastWriteValue = [(POPAnimationValueEvent *)writeEvents.lastObject value]; + STAssertEqualObjects(lastWriteValue, anim.toValue, @"unexpected lastWriteValue; expected:%@ actual:%@", anim.toValue, lastWriteValue); +} + +- (void)testAnimationContinuation +{ + POPAnimatable *circle = [POPAnimatable new]; + POPSpringAnimation *anim = [POPSpringAnimation animation]; + anim.property = self.radiusProperty; + anim.fromValue = @0.0; + anim.toValue = @1.0; + anim.velocity = @10.0; + anim.springBounciness = 4; + + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + [circle pop_addAnimation:anim forKey:@"key"]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 0.25, 0.05); + + NSArray *didReachToEvents = [tracer eventsWithType:kPOPAnimationEventDidReachToValue]; + NSArray *stopEvents = [tracer eventsWithType:kPOPAnimationEventDidStop]; + + // assert did reach but not stop + STAssertTrue(1 == didReachToEvents.count, @"unexpected didReachToEvents count %@", didReachToEvents); + STAssertTrue(0 == stopEvents.count, @"unexpected stopEvents count %@", stopEvents); + + // update to value continuing animation + anim.toValue = @0.0; + POPAnimatorRenderDuration(self.animator, self.beginTime, 2.0, 0.1); + [tracer stop]; + + // two did reach to events + didReachToEvents = [tracer eventsWithType:kPOPAnimationEventDidReachToValue]; + STAssertTrue(2 == didReachToEvents.count, @"unexpected didReachToEvents count %@", didReachToEvents); + + // first event value > animation to value + id firstDidReachValue = [(POPAnimationValueEvent *)didReachToEvents.firstObject value]; + STAssertTrue(NSOrderedAscending == [anim.toValue compare:firstDidReachValue], @"unexpected firstDidReachValue; toValue:%@ actual:%@", anim.toValue, firstDidReachValue); + + // second event value < animation to value + id lastDidReachValue = [(POPAnimationValueEvent *)didReachToEvents.lastObject value]; + STAssertTrue(NSOrderedDescending == [anim.toValue compare:lastDidReachValue], @"unexpected lastDidReachValue; toValue:%@ actual:%@", anim.toValue, lastDidReachValue); + + // did stop event + stopEvents = [tracer eventsWithType:kPOPAnimationEventDidStop]; + STAssertTrue(1 == stopEvents.count, @"unexpected stopEvents count %@", stopEvents); + STAssertEqualObjects([(POPAnimationValueEvent *)stopEvents.lastObject value], @YES, @"unexpected stop event: %@", stopEvents.lastObject); +} + +- (void)testRoundingFactor +{ + POPAnimatable *circle = [POPAnimatable new]; + + { + // non retina, additive & non-additive + BOOL additive = NO; + + LStart: + POPBasicAnimation *anim = [POPBasicAnimation animation]; + anim.property = self.radiusProperty; + anim.fromValue = @0.0; + anim.toValue = @1.0; + anim.roundingFactor = 1.0; + anim.additive = additive; + + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + [circle pop_addAnimation:anim forKey:@"key"]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 0.25, 0.05); + + NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite]; + BOOL containValue = POPAnimationEventsContainValue(writeEvents, @0.5); + STAssertFalse(containValue, @"unexpected write value %@", writeEvents); + + if (!additive) { + additive = YES; + goto LStart; + } + } + + { + // retina, additive & non-additive + BOOL additive = NO; + + LStartRetina: + POPBasicAnimation *anim = [POPBasicAnimation animation]; + anim.property = self.radiusProperty; + anim.fromValue = @0.0; + anim.toValue = @1.0; + anim.roundingFactor = 0.5; + + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + [circle pop_addAnimation:anim forKey:@"key"]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 0.25, 0.05); + + NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite]; + BOOL containValue = POPAnimationEventsContainValue(writeEvents, @0.5); + STAssertTrue(containValue, @"unexpected write value %@", writeEvents); + + if (!additive) { + additive = YES; + goto LStartRetina; + } + } +} + +- (void)testAdditiveAnimation +{ + const CGFloat baseValue = 1.; + const CGFloat fromValue = 1.; + const CGFloat toValue = 2.; + + POPAnimatable *circle = [POPAnimatable new]; + circle.radius = baseValue; + + POPBasicAnimation *anim; + anim = [POPBasicAnimation animation]; + anim.property = self.radiusProperty; + anim.fromValue = @(fromValue); + anim.toValue = @(toValue); + anim.additive = YES; + + [circle startRecording]; + [circle pop_addAnimation:anim forKey:@"key1"]; + + POPAnimatorRenderDuration(self.animator, self.beginTime, 0.5, 0.05); + + NSArray *writeEvents = [circle recordedValuesForKey:@"radius"]; + CGFloat firstValue = [[writeEvents firstObject] floatValue]; + CGFloat lastValue = [[writeEvents lastObject] floatValue]; + + STAssertTrue(firstValue >= baseValue + fromValue, @"write value expected:%f actual:%f", baseValue + fromValue, firstValue); + STAssertTrue(lastValue == baseValue + toValue, @"write value expected:%f actual:%f", baseValue + toValue, lastValue); +} + +- (void)testNilKey +{ + POPBasicAnimation *anim = FBTestLinearPositionAnimation(self.beginTime); + + // avoid fractional values; simplify verification + anim.roundingFactor = 1.0; + + id layer = [OCMockObject niceMockForClass:[CALayer class]]; + [[layer expect] setPosition:FBTestInterpolateLinear(Vector2r([anim.fromValue CGPointValue]), Vector2r([anim.toValue CGPointValue]), 0.25).cg_point()]; + [[layer expect] setPosition:FBTestInterpolateLinear(Vector2r([anim.fromValue CGPointValue]), Vector2r([anim.toValue CGPointValue]), 0.5).cg_point()]; + [[layer expect] setPosition:FBTestInterpolateLinear(Vector2r([anim.fromValue CGPointValue]), Vector2r([anim.toValue CGPointValue]), 0.75).cg_point()]; + [[layer expect] setPosition:[anim.toValue CGPointValue]]; + + // verify nil key can be added, same as CA + [layer pop_addAnimation:anim forKey:nil]; + + // verify attempting to remove nil key is a noop, same as CA + STAssertNoThrow([layer pop_removeAnimationForKey:nil], @"unexpected exception"); + + POPAnimatorRenderDuration(self.animator, self.beginTime, 1, 0.25); + [layer verify]; +} + +- (void)testIntegerAnimation +{ + const int toValue = 1; + NSNumber *boxedToValue = @1.0; + + POPBasicAnimation *anim; + + // literal + anim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity]; + STAssertNoThrow(anim.toValue = @(toValue), @"unexpected exception"); + STAssertEqualObjects(anim.toValue, boxedToValue, @"expected equality; value1:%@ value2:%@", anim.toValue, boxedToValue); + + // integer + anim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity]; + STAssertNoThrow(anim.toValue = @(toValue), @"unexpected exception"); + STAssertEqualObjects(anim.toValue, boxedToValue, @"expected equality; value1:%@ value2:%@", anim.toValue, boxedToValue); + + // short + anim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity]; + STAssertNoThrow(anim.toValue = @(toValue), @"unexpected exception"); + STAssertEqualObjects(anim.toValue, boxedToValue, @"expected equality; value1:%@ value2:%@", anim.toValue, boxedToValue); + + // unsigned short + anim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity]; + STAssertNoThrow(anim.toValue = @(toValue), @"unexpected exception"); + STAssertEqualObjects(anim.toValue, boxedToValue, @"expected equality; value1:%@ value2:%@", anim.toValue, boxedToValue); + + // int + anim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity]; + STAssertNoThrow(anim.toValue = @(toValue), @"unexpected exception"); + STAssertEqualObjects(anim.toValue, boxedToValue, @"expected equality; value1:%@ value2:%@", anim.toValue, boxedToValue); + + // unsigned int + anim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity]; + STAssertNoThrow(anim.toValue = @(toValue), @"unexpected exception"); + STAssertEqualObjects(anim.toValue, boxedToValue, @"expected equality; value1:%@ value2:%@", anim.toValue, boxedToValue); + + // long + anim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity]; + STAssertNoThrow(anim.toValue = @(toValue), @"unexpected exception"); + STAssertEqualObjects(anim.toValue, boxedToValue, @"expected equality; value1:%@ value2:%@", anim.toValue, boxedToValue); + + // unsigned long + anim = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity]; + STAssertNoThrow(anim.toValue = @(toValue), @"unexpected exception"); + STAssertEqualObjects(anim.toValue, boxedToValue, @"expected equality; value1:%@ value2:%@", anim.toValue, boxedToValue); + + anim.fromValue = @0; + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + CALayer *layer = [CALayer layer]; + layer.opacity = 0; + [layer pop_addAnimation:anim forKey:nil]; + + POPAnimatorRenderDuration(self.animator, self.beginTime, 1, 0.1); + + // verify writes happened + NSArray *writeEvents = tracer.writeEvents; + STAssertTrue(writeEvents.count == 5, @"unexpected events:%@", writeEvents); + + // verify final value + STAssertEqualObjects([layer valueForKey:@"opacity"], anim.toValue, @"expected equality; value1:%@ value2:%@", [layer valueForKey:@"opacity"], anim.toValue); +} + +@end diff --git a/pop-tests/POPAnimationTestsExtras.h b/pop-tests/POPAnimationTestsExtras.h new file mode 100644 index 00000000..3d95fd55 --- /dev/null +++ b/pop-tests/POPAnimationTestsExtras.h @@ -0,0 +1,18 @@ +/** + 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 "POPVector.h" +@class POPAnimator; +@class POPBasicAnimation; + +extern void POPAnimatorRenderTimes(POPAnimator *animator, CFTimeInterval beginTime, NSArray *times); +extern void POPAnimatorRenderDuration(POPAnimator *animator, CFAbsoluteTime beginTime, CFTimeInterval duration, CFTimeInterval step); + +extern POPBasicAnimation *FBTestLinearPositionAnimation(CFTimeInterval beginTime = 0); +extern POP::Vector2r FBTestInterpolateLinear(POP::Vector2r start, POP::Vector2r end, CGFloat progress); diff --git a/pop-tests/POPAnimationTestsExtras.mm b/pop-tests/POPAnimationTestsExtras.mm new file mode 100644 index 00000000..1a20f32f --- /dev/null +++ b/pop-tests/POPAnimationTestsExtras.mm @@ -0,0 +1,46 @@ +/** + 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 "POPAnimationTestsExtras.h" + +#import +#import + +void POPAnimatorRenderTimes(POPAnimator *animator, CFTimeInterval beginTime, NSArray *times) +{ + for (NSNumber *time in times) { + [animator renderTime:beginTime + time.doubleValue]; + } +} + +void POPAnimatorRenderDuration(POPAnimator *animator, CFTimeInterval beginTime, CFTimeInterval duration, CFTimeInterval step) +{ + NSCAssert(step > 0, @"unexpected step %f", step); + CFTimeInterval time = 0; + while(time <= duration) { + [animator renderTime:beginTime + time]; + time += step; + } +} + +POPBasicAnimation *FBTestLinearPositionAnimation(CFTimeInterval beginTime) +{ + POPBasicAnimation *anim = [POPBasicAnimation linearAnimation]; + anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerPosition]; + anim.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)]; + anim.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)]; + anim.duration = 1; + anim.beginTime = beginTime; + return anim; +} + +POP::Vector2r FBTestInterpolateLinear(POP::Vector2r start, POP::Vector2r end, CGFloat progress) +{ + return start + ((end - start) * progress); +} diff --git a/pop-tests/POPBaseAnimationTests.h b/pop-tests/POPBaseAnimationTests.h new file mode 100644 index 00000000..75bff862 --- /dev/null +++ b/pop-tests/POPBaseAnimationTests.h @@ -0,0 +1,39 @@ +/** + 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 + +@class CALayer; +@class POPAnimator; +@class POPAnimatableProperty; + +@interface POPBaseAnimationTests : SenTestCase + +// two layers for test use +@property (strong, nonatomic) CALayer *layer1, *layer2; + +// the animator to use for rendering +@property (strong, nonatomic) POPAnimator *animator; + +// the time tests began +@property (assign, nonatomic) CFTimeInterval beginTime; + +// radius animatable property +@property (strong, nonatomic) POPAnimatableProperty *radiusProperty; + +@end + +// max frame count required for animations to converge +extern NSUInteger kPOPAnimationConvergenceMaxFrameCount; + +// counts the number of events of value within epsilon, starting from end +extern NSUInteger POPAnimationCountLastEventValues(NSArray *events, NSNumber *value, float epsilon = 0); + +// returns YES if array of value events contain specified value +extern BOOL POPAnimationEventsContainValue(NSArray *events, NSNumber *value); diff --git a/pop-tests/POPBaseAnimationTests.mm b/pop-tests/POPBaseAnimationTests.mm new file mode 100644 index 00000000..3ae112a1 --- /dev/null +++ b/pop-tests/POPBaseAnimationTests.mm @@ -0,0 +1,76 @@ +/** + 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 "POPBaseAnimationTests.h" + +#import + +#import + +#import + +#import "POPAnimatable.h" +#import "POPAnimationTestsExtras.h" + +@implementation POPBaseAnimationTests +{ + CALayer *_layer1; + CALayer *_layer2; + POPAnimator *_animator; + CFTimeInterval _beginTime; + POPAnimatableProperty *_radiusProperty; +} + +- (void)setUp +{ + [super setUp]; + _layer1 = [[CALayer alloc] init]; + _layer2 = [[CALayer alloc] init]; + _animator = [POPAnimator sharedAnimator]; + _radiusProperty = [POPAnimatableProperty propertyWithName:@"radius" initializer:^(POPMutableAnimatableProperty *prop){ + prop.readBlock = ^(POPAnimatable *obj, CGFloat values[]) { + values[0] = [obj radius]; + }; + prop.writeBlock = ^(POPAnimatable *obj, const CGFloat values[]) { + obj.radius = values[0]; + }; + prop.threshold = 0.01; + }]; + _beginTime = CACurrentMediaTime(); +} + +@end + +NSUInteger kPOPAnimationConvergenceMaxFrameCount = 12; // 12 frames, ~200ms at 1/60fps, the user perseption threshold + +NSUInteger POPAnimationCountLastEventValues(NSArray *events, NSNumber *value, float epsilon) +{ + __block NSUInteger count = 0; + [events enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(POPAnimationValueEvent *event, NSUInteger idx, BOOL *ptrStop) { + + BOOL match = 0 == epsilon ? [event.value isEqualToValue:value] : fabsf([event.value floatValue] - [value floatValue]) < epsilon; + if (!match) { + *ptrStop = YES; + } else { + count++; + } + }]; + return count; +} + +BOOL POPAnimationEventsContainValue(NSArray *events, NSNumber *value) +{ + for (POPAnimationValueEvent *event in events) { + if ([event.value isEqual:value]) { + return YES; + } + } + return NO; +} + diff --git a/pop-tests/POPCustomAnimationTests.mm b/pop-tests/POPCustomAnimationTests.mm new file mode 100644 index 00000000..10dfa566 --- /dev/null +++ b/pop-tests/POPCustomAnimationTests.mm @@ -0,0 +1,161 @@ +/** + 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 + +#import +#import + +#import "POPAnimatable.h" +#import "POPAnimationTestsExtras.h" +#import "POPBaseAnimationTests.h" + +static const CGFloat epsilon = 0.0001f; + +@interface POPCustomAnimationTests : POPBaseAnimationTests +@end + +@implementation POPCustomAnimationTests + +- (void)testCallbackFinished +{ + static NSString * const key = @"key"; + static CFTimeInterval const timeInterval = 0.1; + + __block NSUInteger callbackCount = 0; + + // animation + POPCustomAnimation *anim = [POPCustomAnimation animationWithBlock:^BOOL(id target, POPCustomAnimation *animation) { + // validate elapsed time + STAssertEqualsWithAccuracy(animation.elapsedTime, timeInterval, epsilon, @"expected elapsedTime:%f %@", timeInterval, animation); + + // increment callback count + callbackCount++; + + return callbackCount < 3; + }]; + + anim.beginTime = self.beginTime; + + // delegate + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + + // expect start, progress & stop to all be called + [[delegate expect] pop_animationDidStart:anim]; + [[delegate expect] pop_animationDidStop:anim finished:YES]; + [[delegate expect] pop_animationDidApply:anim]; + + anim.delegate = delegate; + + // layer + id layer = [OCMockObject niceMockForClass:[CALayer class]]; + [layer pop_addAnimation:anim forKey:key]; + + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + POPAnimatorRenderDuration(self.animator, self.beginTime, 5, 0.1); + STAssertTrue(callbackCount == 3, @"unexpected callbackCount:%d", callbackCount); + + NSArray *startEvents = [tracer eventsWithType:kPOPAnimationEventDidStart]; + STAssertTrue(1 == startEvents.count, @"unexpected startEvents count %@", startEvents); + + NSArray *stopEvents = [tracer eventsWithType:kPOPAnimationEventDidStop]; + STAssertTrue(1 == stopEvents.count, @"unexpected stopEvents count %@", stopEvents); + + [layer verify]; + [delegate verify]; +} + +- (void)testCallbackCancelled +{ + static NSString * const key = @"key"; + static CFTimeInterval const timeInterval = 0.1; + + __block NSUInteger callbackCount = 0; + + // animation + POPCustomAnimation *anim = [POPCustomAnimation animationWithBlock:^BOOL(id target, POPCustomAnimation *animation) { + // validate elapsed time acruel + STAssertEqualsWithAccuracy(animation.elapsedTime, timeInterval, epsilon, @"expected elapsedTime:%f %@", timeInterval, animation); + + // increment callback count + callbackCount++; + + if (callbackCount == 3) { + [target pop_removeAnimationForKey:key]; + } + + return callbackCount < 3; + }]; + + anim.beginTime = self.beginTime; + + // delegate + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + + // expect start, progress & stop to all be called + [[delegate expect] pop_animationDidStart:anim]; + [[delegate expect] pop_animationDidStop:anim finished:NO]; + [[delegate expect] pop_animationDidApply:anim]; + + anim.delegate = delegate; + + // layer + id layer = [OCMockObject niceMockForClass:[CALayer class]]; + [layer pop_addAnimation:anim forKey:key]; + + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + POPAnimatorRenderDuration(self.animator, self.beginTime, 5, 0.1); + STAssertTrue(callbackCount == 3, @"unexpected callbackCount:%d", callbackCount); + + NSArray *startEvents = [tracer eventsWithType:kPOPAnimationEventDidStart]; + STAssertTrue(1 == startEvents.count, @"unexpected startEvents count %@", startEvents); + + NSArray *stopEvents = [tracer eventsWithType:kPOPAnimationEventDidStop]; + STAssertTrue(1 == stopEvents.count, @"unexpected stopEvents count %@", stopEvents); + + [layer verify]; + [delegate verify]; +} + +- (void)testAssociation +{ + static NSString * const key = @"key"; + + __block id blockTarget = nil; + + POPCustomAnimation *anim = [POPCustomAnimation animationWithBlock:^(id target, POPCustomAnimation *animation) { + blockTarget = target; + return YES; + }]; + + id layer = [OCMockObject niceMockForClass:[CALayer class]]; + + [layer pop_addAnimation:anim forKey:key]; + + // verify animation & key + STAssertTrue(anim == [layer pop_animationForKey:key], @"expected:%@ actual:%@", anim, [layer pop_animationForKey:key]); + STAssertTrue([[layer pop_animationKeys] containsObject:key], @"expected:%@ actual:%@", key, [layer pop_animationKeys]); + + POPAnimatorRenderDuration(self.animator, self.beginTime, 1, 0.1); + + STAssertEqualObjects(layer, blockTarget, @"expected:%@ actual:%@", layer, blockTarget); + + // remove animations + [layer pop_removeAnimationForKey:key]; + + // verify animation & key + STAssertFalse(anim == [layer pop_animationForKey:key], @"expected:%@ actual:%@", nil, [layer pop_animationForKey:key]); + STAssertFalse([[layer pop_animationKeys] containsObject:key], @"expected:%@ actual:%@", nil, [layer pop_animationKeys]); +} + +@end diff --git a/pop-tests/POPDecayAnimationTests.mm b/pop-tests/POPDecayAnimationTests.mm new file mode 100644 index 00000000..03c9690c --- /dev/null +++ b/pop-tests/POPDecayAnimationTests.mm @@ -0,0 +1,462 @@ +/** + 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 + +#import +#import + +#import +#import + +#import "POPAnimatable.h" +#import "POPAnimationTestsExtras.h" +#import "POPBaseAnimationTests.h" + +@interface POPDecayAnimationTests : POPBaseAnimationTests +@end + +@implementation POPDecayAnimationTests + +static NSString *animationKey = @"key"; +static const CGFloat epsilon = 0.0001f; + +- (POPDecayAnimation *)_positionAnimation +{ + POPDecayAnimation *anim = [POPDecayAnimation animation]; + anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerPosition]; + anim.fromValue = [NSValue valueWithCGPoint:CGPointZero]; + anim.velocity = [NSValue valueWithCGPoint:CGPointMake(7223.021, 7223.021)]; + anim.deceleration = 0.998000; + return anim; +} + +- (POPDecayAnimation *)_positionXAnimation +{ + POPDecayAnimation *anim = [POPDecayAnimation animation]; + anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerPositionX]; + anim.fromValue = @0.; + anim.velocity = @7223.021; + anim.deceleration = 0.998000; + return anim; +} + +- (POPDecayAnimation *)_positionYAnimation +{ + POPDecayAnimation *anim = self._positionXAnimation; + anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerPositionY]; + return anim; +} + +- (void)testConvergence +{ + POPAnimatable *circle = [POPAnimatable new]; + POPDecayAnimation *anim = self._positionXAnimation; + + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + [circle pop_addAnimation:anim forKey:@"key"]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 10.0, 1.0/60.0); + [tracer stop]; + + // did reach to value + POPAnimationValueEvent *didReachToEvent = [[tracer eventsWithType:kPOPAnimationEventDidReachToValue] lastObject]; + STAssertEqualObjects(didReachToEvent.value, anim.toValue, @"unexpected did reach to event: %@ anim:%@", didReachToEvent, anim); + + // finished + POPAnimationValueEvent *stopEvent = [[tracer eventsWithType:kPOPAnimationEventDidStop] lastObject]; + STAssertEqualObjects(stopEvent.value, @YES, @"unexpected stop event %@", stopEvent); + + // all write values monotonically increasing + NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite]; + POPAnimationValueEvent *lastWriteEvent = nil; + for (POPAnimationValueEvent *writeEvent in writeEvents) { + if (lastWriteEvent) { + NSComparisonResult result = [lastWriteEvent.value compare:writeEvent.value]; + STAssertTrue(NSOrderedAscending == result || NSOrderedSame == result, @"write event values not monotonically increasing current:%@ last:%@ all:%@", writeEvent, lastWriteEvent, writeEvents); + } + lastWriteEvent = writeEvent; + } + + // convergence threshold + NSUInteger toValueFrameCount = POPAnimationCountLastEventValues(writeEvents, anim.toValue, anim.property.threshold); + STAssertTrue(toValueFrameCount <= kPOPAnimationConvergenceMaxFrameCount, @"unexpected convergence; toValueFrameCount: %d", toValueFrameCount); +} + +- (void)testConvergenceNegativeVelocity +{ + POPAnimatable *circle = [POPAnimatable new]; + POPDecayAnimation *anim = self._positionXAnimation; + anim.velocity = @-7223.021; + + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + [circle pop_addAnimation:anim forKey:@"key"]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 10.0, 1.0/60.0); + [tracer stop]; + + // did reach to value + POPAnimationValueEvent *didReachToEvent = [[tracer eventsWithType:kPOPAnimationEventDidReachToValue] lastObject]; + STAssertEqualObjects(didReachToEvent.value, anim.toValue, @"unexpected did reach to event: %@ anim:%@", didReachToEvent, anim); + + // finished + POPAnimationValueEvent *stopEvent = [[tracer eventsWithType:kPOPAnimationEventDidStop] lastObject]; + STAssertEqualObjects(stopEvent.value, @YES, @"unexpected stop event %@", stopEvent); + + // all write values monotonically increasing + NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite]; + POPAnimationValueEvent *lastWriteEvent = nil; + for (POPAnimationValueEvent *writeEvent in writeEvents) { + if (lastWriteEvent) { + NSComparisonResult result = [lastWriteEvent.value compare:writeEvent.value]; + STAssertTrue(NSOrderedDescending == result || NSOrderedSame == result, @"write event values not monotonically decreasing current:%@ last:%@ all:%@", writeEvent, lastWriteEvent, writeEvents); + } + lastWriteEvent = writeEvent; + } + + // convergence threshold + NSUInteger toValueFrameCount = POPAnimationCountLastEventValues(writeEvents, anim.toValue, anim.property.threshold); + STAssertTrue(toValueFrameCount <= kPOPAnimationConvergenceMaxFrameCount, @"unexpected convergence; toValueFrameCount: %d", toValueFrameCount); +} + +- (void)test2DConvergence +{ + POPDecayAnimation *animX = self._positionXAnimation; + POPDecayAnimation *animY = self._positionYAnimation; + STAssertEquals(animX.duration, animY.duration, @"unexpected durations animX:%@ animY:%@", animX, animY); + STAssertEqualObjects(animX.toValue, animY.toValue, @"unexpected toValue animX:%@ animY:%@", animX, animY); + + POPDecayAnimation *anim = self._positionAnimation; + CFTimeInterval duration = anim.duration; + STAssertEqualsWithAccuracy(animX.duration, duration, epsilon, @"unexpected durations animX:%@ anim:%@", animX, anim); + STAssertEqualObjects(animX.toValue, @([anim.toValue CGPointValue].x), @"unexpected toValue animX:%@ anim:%@", animX, anim); + + POPAnimatable *circle = [POPAnimatable new]; + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + [circle pop_addAnimation:anim forKey:@"key"]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 10.0, 1.0/60.0); + [tracer stop]; + + // did reach to value + POPAnimationValueEvent *didReachToEvent = [[tracer eventsWithType:kPOPAnimationEventDidReachToValue] lastObject]; + STAssertEqualObjects(didReachToEvent.value, anim.toValue, @"unexpected did reach to event: %@ anim:%@", didReachToEvent, anim); + + // finished + POPAnimationValueEvent *stopEvent = [[tracer eventsWithType:kPOPAnimationEventDidStop] lastObject]; + STAssertEqualObjects(stopEvent.value, @YES, @"unexpected stop event %@", stopEvent); + + // increase X velocity + anim.velocity = [NSValue valueWithCGPoint:CGPointMake(7223.021 + 1000, 7223.021)]; + STAssertTrue(anim.duration > duration, @"unexpected duration expected:%f anim:%@", duration, anim); + + // increase Y velocity + anim.velocity = [NSValue valueWithCGPoint:CGPointMake(7223.021, 7223.021 + 1000)]; + STAssertTrue(anim.duration > duration, @"unexpected duration expected:%f anim:%@", duration, anim); +} + +- (void)testRemovedOnCompletionNoStartStopBasics +{ + static NSString *animationKey = @"key"; + + CALayer *layer = self.layer1; + POPAnimation *anim = self._positionXAnimation; + POPAnimationTracer *tracer = anim.tracer; + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + + // cleanup + [layer pop_removeAllAnimations]; + + // configure animation + anim.removedOnCompletion = NO; + anim.delegate = delegate; + + __block BOOL completionBlock = NO; + __block BOOL completionBlockFinished = NO; + anim.completionBlock = ^(POPAnimation *a, BOOL finished) { + completionBlock = YES; + completionBlockFinished = finished; + }; + + // start tracer + [tracer start]; + + // expect start and stopped + [[delegate expect] pop_animationDidStart:anim]; + [[delegate expect] pop_animationDidStop:anim finished:YES]; + + [layer pop_addAnimation:anim forKey:animationKey]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 20.0, 1.0/60.0); + NSArray *allEvents = tracer.allEvents; + + // verify delegate + [delegate verify]; + STAssertTrue(completionBlock, @"completion block did not execute %@", allEvents); + STAssertTrue(completionBlockFinished, @"completion block did not finish %@", allEvents); + + // assert animation has not been removed + STAssertTrue(anim == [layer pop_animationForKey:animationKey], @"expected animation on layer animations:%@", [layer pop_animationKeys]); +} + +- (void)testRemovedOnCompletionNoContinuations +{ + static NSString *animationKey = @"key"; + static NSArray *velocities = @[@50.0, @100.0, @20.0, @80.0]; + static NSArray *durations = @[@2.0, @0.5, @0.5, @2.0]; + + CALayer *layer = self.layer1; + POPDecayAnimation *anim = self._positionXAnimation; + POPAnimationTracer *tracer = anim.tracer; + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + + // cleanup + [layer pop_removeAllAnimations]; + + // configure animation + anim.removedOnCompletion = NO; + anim.delegate = delegate; + + // start tracer + [tracer start]; + + __block CFTimeInterval beginTime; + __block BOOL completionBlock = NO; + __block BOOL completionBlockFinished = NO; + + [velocities enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *ptrStop) { + anim.velocity = obj; + + if (0 == idx) { + [tracer reset]; + + // starts and stops + [[delegate expect] pop_animationDidStart:anim]; + [[delegate expect] pop_animationDidStop:anim finished:YES]; + + anim.completionBlock = ^(POPAnimation *a, BOOL finished) { + completionBlock = YES; + completionBlockFinished = finished; + }; + + [layer pop_addAnimation:anim forKey:animationKey]; + + beginTime = self.beginTime; + CFTimeInterval dt = [durations[idx] doubleValue]; + POPAnimatorRenderDuration(self.animator, beginTime, dt, 1.0/60.0); + beginTime += dt; + + NSArray *allEvents = tracer.allEvents; + NSArray *didReachEvents = [tracer eventsWithType:kPOPAnimationEventDidReachToValue]; + + // verify delegate + [delegate verify]; + STAssertTrue(1 == didReachEvents.count, @"unexpected didReachEvents %@", didReachEvents); + STAssertTrue(completionBlock, @"completion block did not execute %@", allEvents); + STAssertTrue(completionBlockFinished, @"completion block did not finish %@", allEvents); + } else if (velocities.count - 1 == idx) { + // continue stoped animation + [tracer reset]; + completionBlock = NO; + completionBlockFinished = NO; + [[delegate expect] pop_animationDidStop:anim finished:YES]; + + CFTimeInterval dt = [durations[idx] doubleValue]; + POPAnimatorRenderDuration(self.animator, beginTime, dt, 1.0/60.0); + beginTime += dt; + + NSArray *allEvents = tracer.allEvents; + NSArray *didReachEvents = [tracer eventsWithType:kPOPAnimationEventDidReachToValue]; + + // verify delegate + [delegate verify]; + STAssertTrue(1 == didReachEvents.count, @"unexpected didReachEvents %@", didReachEvents); + STAssertTrue(completionBlock, @"completion block did not execute %@", allEvents); + STAssertTrue(completionBlockFinished, @"completion block did not finish %@", allEvents); + } else { + // continue stoped (idx = 1) or started animation + if (1 == idx) { + [[delegate expect] pop_animationDidStart:anim]; + } + + // reset state + [tracer reset]; + completionBlock = NO; + completionBlockFinished = NO; + + CFTimeInterval dt = [durations[idx] doubleValue]; + POPAnimatorRenderDuration(self.animator, beginTime, dt, 1.0/60.0); + beginTime += dt; + + NSArray *allEvents = tracer.allEvents; + NSArray *didReachEvents = [tracer eventsWithType:kPOPAnimationEventDidReachToValue]; + + // verify delegate + [delegate verify]; + STAssertTrue(0 == didReachEvents.count, @"unexpected didReachEvents %@", didReachEvents); + STAssertFalse(completionBlock, @"completion block did not execute %@ %@", anim, allEvents); + STAssertFalse(completionBlockFinished, @"completion block did not finish %@ %@", anim, allEvents); + } + + // assert animation has not been removed + STAssertTrue(anim == [layer pop_animationForKey:animationKey], @"expected animation on layer animations:%@", [layer pop_animationKeys]); + }]; +} + +- (void)testNoOperationAnimation +{ + const CGPoint initialValue = CGPointMake(100, 100); + + CALayer *layer = self.layer1; + layer.position = initialValue; + [layer pop_removeAllAnimations]; + + POPDecayAnimation *anim = [POPDecayAnimation animation]; + anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerPosition]; + + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + anim.delegate = delegate; + + // starts and stops + [[delegate expect] pop_animationDidStart:anim]; + [[delegate expect] pop_animationDidStop:anim finished:YES]; + + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + [layer pop_addAnimation:anim forKey:animationKey]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 5, 1.0/60.0); + + // verify delegate + [delegate verify]; + + // verify number values + NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite]; + for (POPAnimationValueEvent *writeEvent in writeEvents) { + STAssertEqualObjects(writeEvent.value, [NSValue valueWithCGPoint:initialValue], @"unexpected write event:%@ anim:%@", writeEvent, anim); + } +} + +- (void)testContinuation +{ + POPDecayAnimation *anim = [POPDecayAnimation animation]; + anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerPositionX]; + anim.fromValue = @0.0; + anim.velocity = @1000.0; + + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + anim.delegate = delegate; + [[delegate expect] pop_animationDidStart:anim]; + + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + CALayer *layer = self.layer1; + [layer pop_addAnimation:anim forKey:animationKey]; + + // run animation, not till completion + POPAnimatorRenderDuration(self.animator, self.beginTime, 1, 1.0/60.0); + NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite]; + [tracer reset]; + + // verify start delegation + [delegate verify]; + + // update velocity of active animation + anim.velocity = @1000.0; + [[delegate expect] pop_animationDidStop:anim finished:YES]; + + // run animation some more + POPAnimatorRenderDuration(self.animator, CACurrentMediaTime(), 4, 1.0/60.0); + NSArray *moreWriteEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite]; + + // verify stop delegation + [delegate verify]; + + // compare event values + POPAnimationValueEvent *firstEvent = [writeEvents firstObject]; + POPAnimationValueEvent *lastEvent = [writeEvents lastObject]; + POPAnimationValueEvent *firstMoreEvent = [moreWriteEvents firstObject]; + STAssertTrue(NSOrderedAscending == [firstEvent.value compare:lastEvent.value] + && NSOrderedAscending == [lastEvent.value compare:firstMoreEvent.value], @"write event values not monotonically increasing %@ %@ %@", firstEvent, lastEvent, firstMoreEvent); +} + +- (void)testRectSupport +{ + const CGRect fromRect = CGRectMake(0, 0, 0, 0); + + POPDecayAnimation *anim = [POPDecayAnimation animation]; + anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerBounds]; + anim.fromValue = [NSValue valueWithCGRect:fromRect]; + anim.velocity = [NSValue valueWithCGRect:CGRectMake(100, 100, 1000, 1000)]; + + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + anim.delegate = delegate; + + // expect start and stop to be called + [[delegate expect] pop_animationDidStart:anim]; + [[delegate expect] pop_animationDidStop:anim finished:YES]; + + // start tracer + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + CALayer *layer = self.layer1; + [layer pop_addAnimation:anim forKey:animationKey]; + + // run animation + POPAnimatorRenderDuration(self.animator, self.beginTime, 3, 1.0/60.0); + + NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite]; + + // verify delegate + [delegate verify]; + + POPAnimationValueEvent *lastEvent = [writeEvents lastObject]; + CGRect lastRect = [lastEvent.value CGRectValue]; + + STAssertTrue(!CGRectEqualToRect(fromRect, lastRect), @"unexpected last rect value: %@", lastEvent); + STAssertTrue(lastRect.origin.x == lastRect.origin.y && lastRect.size.width == lastRect.size.height && lastRect.origin.x < lastRect.size.width, @"unexpected last rect value: %@", lastEvent); +} + +- (void)testEndValueOnReuse +{ + POPAnimatable *circle = [POPAnimatable new]; + POPDecayAnimation *anim = self._positionXAnimation; + + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + // read out to value + CGFloat toValue = [anim.toValue floatValue]; + [circle pop_addAnimation:anim forKey:@"key"]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 5.0, 1.0/60.0); + + NSArray *stopEvent = [tracer eventsWithType:kPOPAnimationEventDidStop]; + STAssertTrue(1 == stopEvent.count, @"unexpected events:%@", tracer.allEvents); + + CGFloat lastValue = [[(POPAnimationValueEvent *)tracer.writeEvents.lastObject value] floatValue]; + STAssertEqualsWithAccuracy(toValue, lastValue, 0.5, @"expected:%f actual event:%@", tracer.writeEvents.lastObject); + // update animation + anim.fromValue = @([anim.toValue floatValue] - 100); + anim.velocity = @(5000.); + + // and reuse + [tracer reset]; + [circle pop_addAnimation:anim forKey:@"key"]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 5.0, 1.0/60.0); + + // verify decayed passed initial toValue + lastValue = [[(POPAnimationValueEvent *)tracer.writeEvents.lastObject value] floatValue]; + STAssertTrue(lastValue > toValue, @"unexpected last value:%f"); +} + +@end diff --git a/pop-tests/POPEaseInEaseOutAnimationTests.mm b/pop-tests/POPEaseInEaseOutAnimationTests.mm new file mode 100644 index 00000000..e2950462 --- /dev/null +++ b/pop-tests/POPEaseInEaseOutAnimationTests.mm @@ -0,0 +1,91 @@ +/** + 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 + +#import +#import + +#import +#import + +#import "POPAnimatable.h" +#import "POPAnimationTestsExtras.h" +#import "POPBaseAnimationTests.h" + +@interface POPEaseInEaseOutAnimationTests : POPBaseAnimationTests +@end + +@implementation POPEaseInEaseOutAnimationTests + +- (void)testCompletion +{ + // animation + // the default from, to and bounciness values are used + POPBasicAnimation *anim = [POPBasicAnimation easeInEaseOutAnimation]; + anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerScaleXY]; + anim.fromValue = [NSValue valueWithCGPoint:CGPointMake(1.0, 1.0)]; + anim.toValue = [NSValue valueWithCGPoint:CGPointMake(0.97, 0.97)]; + + // delegate + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + + // expect start, progress & stop to all be called + [[delegate expect] pop_animationDidStart:anim]; + [[delegate expect] pop_animationDidStop:anim finished:YES]; + + anim.delegate = delegate; + + CALayer *layer = [CALayer layer]; + [layer pop_addAnimation:anim forKey:@"key"]; + + POPAnimatorRenderTimes(self.animator, self.beginTime, @[@0.0, @0.1, @0.2, @0.4]); + [delegate verify]; +} + +- (void)testRectSupport +{ + const CGRect fromRect = CGRectMake(0, 0, 0, 0); + const CGRect toRect = CGRectMake(100, 200, 200, 400); + + POPBasicAnimation *anim = [POPBasicAnimation easeInEaseOutAnimation]; + anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerBounds]; + anim.fromValue = [NSValue valueWithCGRect:fromRect]; + anim.toValue = [NSValue valueWithCGRect:toRect]; + + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + anim.delegate = delegate; + + // expect start and stop to be called + [[delegate expect] pop_animationDidStart:anim]; + [[delegate expect] pop_animationDidStop:anim finished:YES]; + + // start tracer + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + CALayer *layer = [CALayer layer]; + [layer pop_addAnimation:anim forKey:@""]; + + // run animation + POPAnimatorRenderDuration(self.animator, self.beginTime, 1, 1.0/60.0); + + NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite]; + + // verify delegate + [delegate verify]; + + POPAnimationValueEvent *lastEvent = [writeEvents lastObject]; + CGRect lastRect = [lastEvent.value CGRectValue]; + + // verify last rect is to rect + STAssertTrue(CGRectEqualToRect(lastRect, toRect), @"unexpected last rect value: %@", lastEvent); +} + +@end diff --git a/pop-tests/POPSpringAnimationTests.mm b/pop-tests/POPSpringAnimationTests.mm new file mode 100644 index 00000000..e34cef7b --- /dev/null +++ b/pop-tests/POPSpringAnimationTests.mm @@ -0,0 +1,594 @@ +/** + 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 + +#import +#import + +#import +#import +#import +#import +#import + +#import "POPAnimatable.h" +#import "POPAnimationInternal.h" +#import "POPAnimationTestsExtras.h" +#import "POPBaseAnimationTests.h" +#import "POPCGUtils.h" + +@interface POPSpringAnimationTests : POPBaseAnimationTests +@end + +@implementation POPSpringAnimationTests + +static NSString *animationKey = @"key"; + +- (POPSpringAnimation *)_positionAnimation +{ + POPSpringAnimation *anim = [POPSpringAnimation animation]; + anim.fromValue = @0.0; + anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerPositionX]; + anim.springBounciness = 4.0; + return anim; +} + +- (void)testCompletion +{ + // animation + // the default from, to and bounciness values are used + NSArray *markers = @[@0.5, @0.75, @1.0]; + POPSpringAnimation *anim = [POPSpringAnimation animation]; + anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerPosition]; + anim.progressMarkers = markers; + STAssertEqualObjects(markers, anim.progressMarkers, @"%@ shoudl equal %@", markers, anim.progressMarkers); + + // delegate + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + + // expect start, progress & stop to all be called + [[delegate expect] pop_animationDidStart:anim]; + [[delegate expect] pop_animationDidStop:anim finished:YES]; + + anim.delegate = delegate; + + // layer + id layer = [OCMockObject niceMockForClass:[CALayer class]]; + + // expect position to be called + CGPoint position = CGPointMake(100, 100); + [(CALayer *)[[layer stub] andReturnValue:OCMOCK_VALUE(position)] position]; + [layer pop_addAnimation:anim forKey:@"key"]; + + POPAnimatorRenderTimes(self.animator, self.beginTime, @[@0.0, @0.1, @0.2]); + [layer verify]; + [delegate verify]; +} + +- (void)testConvergence +{ + POPAnimatable *circle = [POPAnimatable new]; + POPSpringAnimation *anim = [POPSpringAnimation animation]; + anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerPositionX]; + anim.fromValue = @0.0; + anim.toValue = @100.0; + anim.velocity = @100.0; + anim.springBounciness = 0.5; + + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + [circle pop_addAnimation:anim forKey:@"key"]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 1.0, 1.0/60.0); + [tracer stop]; + + // finished + POPAnimationValueEvent *stopEvent = [[tracer eventsWithType:kPOPAnimationEventDidStop] lastObject]; + STAssertEqualObjects(stopEvent.value, @YES, @"unexpected stop event %@", stopEvent); + + // convergence threshold + NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite]; + NSUInteger toValueFrameCount = POPAnimationCountLastEventValues(writeEvents, anim.toValue, anim.property.threshold); + STAssertTrue(toValueFrameCount < kPOPAnimationConvergenceMaxFrameCount, @"unexpected convergence; toValueFrameCount: %d", toValueFrameCount); +} + +- (void)testConvergenceRounded +{ + POPAnimatable *circle = [POPAnimatable new]; + POPSpringAnimation *anim = [POPSpringAnimation animation]; + anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerPositionX]; + anim.fromValue = @0.0; + anim.toValue = @100.0; + anim.velocity = @100.0; + anim.springBounciness = 0.5; + anim.roundingFactor = 1.0; + + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + [circle pop_addAnimation:anim forKey:@"key"]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 1.0, 1.0/60.0); + [tracer stop]; + + // finished + POPAnimationValueEvent *stopEvent = [[tracer eventsWithType:kPOPAnimationEventDidStop] lastObject]; + STAssertEqualObjects(stopEvent.value, @YES, @"unexpected stop event %@", stopEvent); + + // convergence threshold + NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite]; + NSUInteger toValueFrameCount = POPAnimationCountLastEventValues(writeEvents, anim.toValue); + STAssertTrue(toValueFrameCount < kPOPAnimationConvergenceMaxFrameCount, @"unexpected convergence; toValueFrameCount: %d", toValueFrameCount); +} + +- (void)testConvergenceClampedRounded +{ + POPAnimatable *circle = [POPAnimatable new]; + POPSpringAnimation *anim = [POPSpringAnimation animation]; + anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerPositionX]; + anim.fromValue = @0.0; + anim.toValue = @100.0; + anim.velocity = @100.0; + anim.springBounciness = 0.5; + anim.roundingFactor = 1.0; + anim.clampMode = kPOPAnimationClampEnd; + + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + [circle pop_addAnimation:anim forKey:@"key"]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 1.0, 1.0/60.0); + [tracer stop]; + + // finished + POPAnimationValueEvent *stopEvent = [[tracer eventsWithType:kPOPAnimationEventDidStop] lastObject]; + STAssertEqualObjects(stopEvent.value, @YES, @"unexpected stop event %@", stopEvent); + + // convergence threshold + NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite]; + NSUInteger toValueFrameCount = POPAnimationCountLastEventValues(writeEvents, anim.toValue); + STAssertTrue(toValueFrameCount < kPOPAnimationConvergenceMaxFrameCount, @"unexpected convergence; toValueFrameCount: %d", toValueFrameCount); +} + +- (void)testRemovedOnCompletionNoStartStopBasics +{ + CALayer *layer = self.layer1; + POPSpringAnimation *anim = self._positionAnimation; + POPAnimationTracer *tracer = anim.tracer; + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + + // cleanup + [layer pop_removeAllAnimations]; + + // configure animation + anim.removedOnCompletion = NO; + anim.toValue = @5.0; + anim.delegate = delegate; + + __block BOOL completionBlock = NO; + __block BOOL completionBlockFinished = NO; + anim.completionBlock = ^(POPAnimation *a, BOOL finished) { + completionBlock = YES; + completionBlockFinished = finished; + }; + + // start tracer + [tracer start]; + + // expect start and stopped + [[delegate expect] pop_animationDidStart:anim]; + [[delegate expect] pop_animationDidStop:anim finished:YES]; + + [layer pop_addAnimation:anim forKey:animationKey]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 20.0, 1.0/60.0); + NSArray *allEvents = tracer.allEvents; + + // verify delegate + [delegate verify]; + STAssertTrue(completionBlock, @"completion block did not execute %@", allEvents); + STAssertTrue(completionBlockFinished, @"completion block did not finish %@", allEvents); + + // assert animation has not been removed + STAssertTrue(anim == [layer pop_animationForKey:animationKey], @"expected animation on layer animations:%@", [layer pop_animationKeys]); +} + +- (void)testRemovedOnCompletionNoContinuations +{ + static NSString *animationKey = @"key"; + static NSArray *toValues = @[@50.0, @100.0, @20.0, @80.0]; + static NSArray *durations = @[@2.0, @0.3, @0.4, @2.0]; + + CALayer *layer = self.layer1; + POPSpringAnimation *anim = self._positionAnimation; + POPAnimationTracer *tracer = anim.tracer; + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + + // cleanup + [layer pop_removeAllAnimations]; + + // configure animation + anim.removedOnCompletion = NO; + anim.delegate = delegate; + + // start tracer + [tracer start]; + + __block CFTimeInterval beginTime; + __block BOOL completionBlock = NO; + __block BOOL completionBlockFinished = NO; + + [toValues enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *ptrStop) { + anim.toValue = obj; + + if (0 == idx) { + [tracer reset]; + + // starts and stops + [[delegate expect] pop_animationDidStart:anim]; + [[delegate expect] pop_animationDidStop:anim finished:YES]; + + anim.completionBlock = ^(POPAnimation *a, BOOL finished) { + completionBlock = YES; + completionBlockFinished = finished; + }; + + [layer pop_addAnimation:anim forKey:animationKey]; + + beginTime = self.beginTime; + CFTimeInterval dt = [durations[idx] doubleValue]; + POPAnimatorRenderDuration(self.animator, beginTime, dt, 1.0/60.0); + beginTime += dt; + + NSArray *allEvents = tracer.allEvents; + NSArray *didReachEvents = [tracer eventsWithType:kPOPAnimationEventDidReachToValue]; + + // verify delegate + [delegate verify]; + STAssertTrue(1 == didReachEvents.count, @"unexpected didReachEvents %@", didReachEvents); + STAssertTrue(completionBlock, @"completion block did not execute %@", allEvents); + STAssertTrue(completionBlockFinished, @"completion block did not finish %@", allEvents); + } else if (toValues.count - 1 == idx) { + // continue stoped animation + [tracer reset]; + completionBlock = NO; + completionBlockFinished = NO; + [[delegate expect] pop_animationDidStop:anim finished:YES]; + + CFTimeInterval dt = [durations[idx] doubleValue]; + POPAnimatorRenderDuration(self.animator, beginTime, dt, 1.0/60.0); + beginTime += dt; + + NSArray *allEvents = tracer.allEvents; + NSArray *didReachEvents = [tracer eventsWithType:kPOPAnimationEventDidReachToValue]; + + // verify delegate + [delegate verify]; + STAssertTrue(1 == didReachEvents.count, @"unexpected didReachEvents %@", didReachEvents); + STAssertTrue(completionBlock, @"completion block did not execute %@", allEvents); + STAssertTrue(completionBlockFinished, @"completion block did not finish %@", allEvents); + } else { + // continue stoped (idx = 1) or started animation + if (1 == idx) { + [[delegate expect] pop_animationDidStart:anim]; + } + + // reset state + [tracer reset]; + completionBlock = NO; + completionBlockFinished = NO; + + CFTimeInterval dt = [durations[idx] doubleValue]; + POPAnimatorRenderDuration(self.animator, beginTime, dt, 1.0/60.0); + beginTime += dt; + + NSArray *allEvents = tracer.allEvents; + NSArray *didReachEvents = [tracer eventsWithType:kPOPAnimationEventDidReachToValue]; + + // verify delegate + [delegate verify]; + STAssertTrue(1 == didReachEvents.count, @"unexpected didReachEvents %@", didReachEvents); + STAssertFalse(completionBlock, @"completion block did not execute %@ %@", anim, allEvents); + STAssertFalse(completionBlockFinished, @"completion block did not finish %@ %@", anim, allEvents); + } + + // assert animation has not been removed + STAssertTrue(anim == [layer pop_animationForKey:animationKey], @"expected animation on layer animations:%@", [layer pop_animationKeys]); + }]; +} + +- (void)testNoOperationAnimation +{ + const CGPoint initialValue = CGPointMake(100, 100); + + CALayer *layer = self.layer1; + layer.position = initialValue; + [layer pop_removeAllAnimations]; + + POPSpringAnimation *anim = [POPSpringAnimation animation]; + anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerPosition]; + + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + anim.delegate = delegate; + + // starts and stops + [[delegate expect] pop_animationDidStart:anim]; + [[delegate expect] pop_animationDidStop:anim finished:YES]; + + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + [layer pop_addAnimation:anim forKey:animationKey]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 5, 1.0/60.0); + + // verify delegate + [delegate verify]; + + // verify number values + NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite]; + for (POPAnimationValueEvent *writeEvent in writeEvents) { + STAssertEqualObjects(writeEvent.value, [NSValue valueWithCGPoint:initialValue], @"unexpected write event:%@ anim:%@", writeEvent, anim); + } +} + +- (void)testLazyValueInitialization +{ + CALayer *layer = self.layer1; + layer.position = CGPointZero; + + POPSpringAnimation *anim = [POPSpringAnimation animation]; + anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerPositionX]; + anim.fromValue = @100.0; + anim.beginTime = self.beginTime + 0.3; + + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + // add animation, but do not start + [layer pop_addAnimation:anim forKey:animationKey]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 0.2, 1.0/60.0); + STAssertNotNil(anim.fromValue, @"unexpected from value %@", anim); + STAssertNil(anim.toValue, @"unexpected to value %@", anim); + + // start animation + POPAnimatorRenderDuration(self.animator, self.beginTime + 0.2, 0.2, 1.0/60.0); + STAssertNotNil(anim.fromValue, @"unexpected from value %@", anim); + STAssertNotNil(anim.toValue, @"unexpected to value %@", anim); + + // continue running animation + anim.fromValue = nil; + anim.toValue = @200.0; + POPAnimatorRenderDuration(self.animator, self.beginTime + 0.4, 0.2, 1.0/60.0); + STAssertNotNil(anim.fromValue, @"unexpected from value %@", anim); + STAssertNotNil(anim.toValue, @"unexpected to value %@", anim); +} + +- (void)testLatentSpring +{ + POPSpringAnimation *translationAnimation = [POPSpringAnimation animation]; + translationAnimation.dynamicsTension = 990; + translationAnimation.dynamicsFriction = 230; + translationAnimation.dynamicsMass = 1.0; + translationAnimation.property = [POPAnimatableProperty propertyWithName:kPOPLayerOpacity]; + translationAnimation.removedOnCompletion = NO; + [self pop_addAnimation:translationAnimation forKey:@"test"]; + POPAnimatorRenderDuration(self.animator, self.beginTime + 0.4, 0.2, 1.0/60.0); +} + +- (void)testRectSupport +{ + const CGRect fromRect = CGRectMake(0, 0, 0, 0); + const CGRect toRect = CGRectMake(100, 200, 200, 400); + const CGRect velocityRect = CGRectMake(1000, 1000, 1000, 1000); + + POPSpringAnimation *anim = [POPSpringAnimation animation]; + anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerBounds]; + anim.fromValue = [NSValue valueWithCGRect:fromRect]; + anim.toValue = [NSValue valueWithCGRect:toRect]; + anim.velocity = [NSValue valueWithCGRect:velocityRect]; + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + anim.delegate = delegate; + + // expect start and stop to be called + [[delegate expect] pop_animationDidStart:anim]; + [[delegate expect] pop_animationDidStop:anim finished:YES]; + + // start tracer + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + CALayer *layer = [CALayer layer]; + [layer pop_addAnimation:anim forKey:@""]; + + // run animation + POPAnimatorRenderDuration(self.animator, self.beginTime, 3, 1.0/60.0); + + NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite]; + + // verify delegate + [delegate verify]; + + POPAnimationValueEvent *lastEvent = [writeEvents lastObject]; + CGRect lastRect = [lastEvent.value CGRectValue]; + + // verify last rect is to rect + STAssertTrue(CGRectEqualToRect(lastRect, toRect), @"unexpected last rect value: %@", lastEvent); +} + +- (void)testColorSupport +{ + CGFloat fromValues[4] = {1, 1, 1, 1}; + CGFloat toValues[4] = {0, 0, 0, 1}; + CGColorRef fromColor = POPCGColorRGBACreate(fromValues); + CGColorRef toColor = POPCGColorRGBACreate(toValues); + + POPSpringAnimation *anim = [POPSpringAnimation animation]; + anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerBounds]; + anim.fromValue = (__bridge_transfer id)fromColor; + anim.toValue = (__bridge_transfer id)toColor; + + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + anim.delegate = delegate; + + // start tracer + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + CALayer *layer = [CALayer layer]; + [layer pop_addAnimation:anim forKey:@""]; + + // run animation + POPAnimatorRenderDuration(self.animator, self.beginTime, 3, 1.0/60.0); + + NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite]; + + // verify delegate + [delegate verify]; + + // expect some interpolation + STAssertTrue(writeEvents.count > 1, @"unexpected write events %@", writeEvents); + POPAnimationValueEvent *lastEvent = [writeEvents lastObject]; + + // verify last written color is to color + CGFloat lastValues[4]; + POPCGColorGetRGBAComponents((__bridge CGColorRef)lastEvent.value, lastValues); + STAssertTrue(lastValues[0] == toValues[0] && lastValues[1] == toValues[1] && lastValues[2] == toValues[2] && lastValues[3] == toValues[3], @"unexpected last color: [r:%f g:%f b:%f a:%f]", lastValues[0], lastValues[1], lastValues[2], lastValues[3]); +} + +static BOOL _floatingPointEqual(CGFloat a, CGFloat b) +{ + CGFloat epsilon = 0.0001; + return fabsf(a - b) < epsilon; +} + +- (void)testBouncinessSpeedToTensionFrictionConversion +{ + CGFloat sampleBounciness = 12.0; + CGFloat sampleSpeed = 5.0; + + CGFloat tension, friction, mass; + [POPSpringAnimation convertBounciness:sampleBounciness speed:sampleSpeed toTension:&tension friction:&friction mass:&mass]; + + CGFloat outBounciness, outSpeed; + [POPSpringAnimation convertTension:tension friction:friction toBounciness:&outBounciness speed:&outSpeed]; + + STAssertTrue(_floatingPointEqual(sampleBounciness, outBounciness) && _floatingPointEqual(sampleSpeed, outSpeed), @"(bounciness, speed) conversion failed. Mapped (%f, %f) back to (%f, %f)", sampleBounciness, sampleSpeed, outBounciness, outSpeed); +} + +- (void)testTensionFrictionToBouncinessSpeedConversion +{ + CGFloat sampleTension = 240.0; + CGFloat sampleFriction = 25.0; + + CGFloat bounciness, speed; + [POPSpringAnimation convertTension:sampleTension friction:sampleFriction toBounciness:&bounciness speed:&speed]; + + CGFloat outTension, outFriction, outMass; + [POPSpringAnimation convertBounciness:bounciness speed:speed toTension:&outTension friction:&outFriction mass:&outMass]; + + STAssertTrue(_floatingPointEqual(sampleTension, outTension) && _floatingPointEqual(sampleFriction, outFriction), @"(tension, friction) conversion failed. Mapped (%f, %f) back to (%f, %f)", sampleTension, sampleFriction, outTension, outFriction); +} + +- (void)testRemovedOnCompletionNoContinuationValues +{ + static CGFloat fromValue = 400.0; + static NSArray *toValues = @[@200.0, @400.0]; + + // configure animation + POPSpringAnimation *anim = self._positionAnimation; + anim.fromValue = [NSNumber numberWithFloat:fromValue]; + anim.toValue = toValues[0]; + anim.removedOnCompletion = NO; + + // run animation, from 400 to 200 + CALayer *layer = [CALayer layer]; + [layer pop_addAnimation:anim forKey:@""]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 3, 1.0/60.0); + + // assert reached to value + STAssertTrue(layer.position.x == [anim.toValue floatValue], @"unexpected value:%@ %@", layer, anim); + + // start tracer + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + // update to value, animate from 200 to 400 + anim.toValue = toValues[1]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 3, 1.0/60.0); + + // verify from 200 to 400 + NSArray *writeEvents = [tracer eventsWithType:kPOPAnimationEventPropertyWrite]; + STAssertTrue(writeEvents.count > 5, @"unexpected frame count %@", writeEvents); + + CGFloat firstValue = [[(POPAnimationValueEvent *)[writeEvents firstObject] value] floatValue]; + CGFloat lastValue = [[(POPAnimationValueEvent *)[writeEvents lastObject] value] floatValue]; + STAssertEqualsWithAccuracy(((CGFloat)[toValues[0] floatValue]), firstValue, 10, @"unexpected first value %@", writeEvents); + STAssertEqualsWithAccuracy(((CGFloat)[toValues[1] floatValue]), lastValue, 10, @"unexpected last value %@", writeEvents); +} + +- (void)testNilColor +{ + POPSpringAnimation *anim = [POPSpringAnimation animation]; + anim.property = [POPAnimatableProperty propertyWithName:kPOPLayerBackgroundColor]; + +#if TARGET_OS_IPHONE + anim.toValue = (__bridge id)[UIColor redColor].CGColor; +#else + anim.toValue = (__bridge id)[NSColor redColor].CGColor; +#endif + + // start tracer + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + // run animation + CALayer *layer = [CALayer layer]; + [layer pop_addAnimation:anim forKey:@""]; + POPAnimatorRenderDuration(self.animator, self.beginTime, 3, 1.0/60.0); + + // verify valid from color exists + CGColorRef fromColor = (__bridge CGColorRef)anim.fromValue; + STAssertTrue(fromColor, @"unexpected value %p", fromColor); + + // verify from color clear + CGFloat components[4]; + POPCGColorGetRGBAComponents(fromColor, components); + STAssertTrue(components[0] == 0 && components[1] == 0 && components[2] == 0 && components[3] == 0, @"unexpected components {%f, %f, %f, %f}", components[0], components[1], components[2], components[3]); +} + +- (void)testExcessiveJumpInTime +{ + POPSpringAnimation *anim = self._positionAnimation; + anim.toValue = @(1000.0); + + // start tracer + POPAnimationTracer *tracer = anim.tracer; + [tracer start]; + + id delegate = [OCMockObject niceMockForProtocol:@protocol(POPAnimationDelegate)]; + anim.delegate = delegate; + + // expect start and stop to be called + [[delegate expect] pop_animationDidStart:anim]; + [[delegate expect] pop_animationDidStop:anim finished:YES]; + + CALayer *layer = [CALayer layer]; + [layer pop_addAnimation:anim forKey:animationKey]; + + // render with large time jump + POPAnimatorRenderTimes(self.animator, self.beginTime, @[@0.0, @0.01, @300]); + + // verify start stop + [delegate verify]; + + // verify last write event value + POPAnimationValueEvent *writeEvent = [[tracer eventsWithType:kPOPAnimationEventPropertyWrite] lastObject]; + STAssertEqualObjects(writeEvent.value, anim.toValue, @"unexpected last write event %@", writeEvent); +} + +@end diff --git a/pop-tests/en.lproj/InfoPlist.strings b/pop-tests/en.lproj/InfoPlist.strings new file mode 100644 index 00000000..477b28ff --- /dev/null +++ b/pop-tests/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/pop-tests/pop-tests-Info.plist b/pop-tests/pop-tests-Info.plist new file mode 100644 index 00000000..c317ef52 --- /dev/null +++ b/pop-tests/pop-tests-Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.facebook.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/pop-tests/pop-tests-Prefix.pch b/pop-tests/pop-tests-Prefix.pch new file mode 100644 index 00000000..c331e570 --- /dev/null +++ b/pop-tests/pop-tests-Prefix.pch @@ -0,0 +1,10 @@ +// +// Prefix header +// +// The contents of this file are implicitly included at the beginning of every source file. +// + +#ifdef __OBJC__ + #import + #import +#endif diff --git a/pop.podspec b/pop.podspec new file mode 100644 index 00000000..71ef6c0e --- /dev/null +++ b/pop.podspec @@ -0,0 +1,16 @@ +Pod::Spec.new do |spec| + spec.name = 'pop' + spec.version = '1.0.0' + spec.license = { :type => 'BSD' } + spec.homepage = 'https://github.com/facebook/pop' + spec.authors = { 'Kimon Tsinteris' => 'kimon@mac.com' } + spec.summary = 'Animation framework for iOS and OS X.' + spec.source = { :git => 'https://github.com/facebook/pop.git', :tag => '1.0.0' } + spec.source_files = 'pop/**/*.{h,m,mm}' + spec.public_header_files = 'pop/{POP,POPAnimatableProperty,POPAnimation,POPAnimationEvent,POPAnimationExtras,POPAnimationTracer,POPAnimator,POPBasicAnimation,POPCustomAnimation,POPDecayAnimation,POPDefines,POPGeometry,POPPropertyAnimation,POPSpringAnimation}.h' + spec.requires_arc = true + spec.social_media_url = 'https://twitter.com/fbOpenSource' + + spec.ios.deployment_target = '6.0' + spec.osx.deployment_target = '10.7' +end diff --git a/pop.xcodeproj/project.pbxproj b/pop.xcodeproj/project.pbxproj new file mode 100644 index 00000000..9125aa78 --- /dev/null +++ b/pop.xcodeproj/project.pbxproj @@ -0,0 +1,1631 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 5E17BB2017457345009842B6 /* POPCustomAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E17BB1E17457345009842B6 /* POPCustomAnimation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5E17BB2117457345009842B6 /* POPCustomAnimation.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5E17BB1F17457345009842B6 /* POPCustomAnimation.mm */; }; + 90AA30B718988BBE00E3BDF7 /* POPSpringSolver.h in Headers */ = {isa = PBXBuildFile; fileRef = 90AA30B618988BBE00E3BDF7 /* POPSpringSolver.h */; }; + ADE1BB0636F940EC8EA7EE57 /* libPods-pop-tests-osx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D411892A480A401FB4D1AA07 /* libPods-pop-tests-osx.a */; }; + BFD8EB4ECB184EF99C22123A /* libPods-pop-tests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D075ECD10B62441CBB9A90F6 /* libPods-pop-tests.a */; }; + EC0AE13116BC73CE001DA2CE /* POPAnimationExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = EC0AE12F16BC73CE001DA2CE /* POPAnimationExtras.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC0AE13216BC73CE001DA2CE /* POPAnimationExtras.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC0AE13016BC73CE001DA2CE /* POPAnimationExtras.mm */; }; + EC19121C162FB53A00E0CC76 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EC19121B162FB53A00E0CC76 /* Foundation.framework */; }; + EC191291162FB5B700E0CC76 /* POPAnimation.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC191289162FB5B700E0CC76 /* POPAnimation.mm */; }; + EC191292162FB5B700E0CC76 /* POPAnimator.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC19128C162FB5B700E0CC76 /* POPAnimator.mm */; }; + EC191293162FB5B700E0CC76 /* POPAnimatableProperty.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC19128F162FB5B700E0CC76 /* POPAnimatableProperty.mm */; }; + EC191295162FB5EC00E0CC76 /* POPAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = EC191288162FB5B700E0CC76 /* POPAnimation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC191296162FB5EC00E0CC76 /* POPAnimationInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = EC19128A162FB5B700E0CC76 /* POPAnimationInternal.h */; }; + EC191297162FB5EC00E0CC76 /* POPAnimator.h in Headers */ = {isa = PBXBuildFile; fileRef = EC19128B162FB5B700E0CC76 /* POPAnimator.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC191298162FB5EC00E0CC76 /* POPAnimatorPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = EC19128D162FB5B700E0CC76 /* POPAnimatorPrivate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC191299162FB5EC00E0CC76 /* POPAnimatableProperty.h in Headers */ = {isa = PBXBuildFile; fileRef = EC19128E162FB5B700E0CC76 /* POPAnimatableProperty.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC1CD95018D80A5C00DE2649 /* POPAnimationPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = EC1CD94E18D80A5C00DE2649 /* POPAnimationPrivate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC1CD95118D80A5C00DE2649 /* POPAnimationPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = EC1CD94E18D80A5C00DE2649 /* POPAnimationPrivate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC35DB2918EE3E820023E077 /* POPAnimationTracer.h in Headers */ = {isa = PBXBuildFile; fileRef = EC35DB2618EE3E820023E077 /* POPAnimationTracer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC35DB2A18EE3E820023E077 /* POPAnimationTracer.h in Headers */ = {isa = PBXBuildFile; fileRef = EC35DB2618EE3E820023E077 /* POPAnimationTracer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC35DB2B18EE3E820023E077 /* POPAnimationTracer.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC35DB2718EE3E820023E077 /* POPAnimationTracer.mm */; }; + EC35DB2C18EE3E820023E077 /* POPAnimationTracer.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC35DB2718EE3E820023E077 /* POPAnimationTracer.mm */; }; + EC35DB2D18EE3E820023E077 /* POPAnimationTracerInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = EC35DB2818EE3E820023E077 /* POPAnimationTracerInternal.h */; }; + EC35DB2E18EE3E820023E077 /* POPAnimationTracerInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = EC35DB2818EE3E820023E077 /* POPAnimationTracerInternal.h */; }; + EC6465D01794B4660014176F /* POPMath.h in Headers */ = {isa = PBXBuildFile; fileRef = EC6465CE1794B4660014176F /* POPMath.h */; }; + EC6465D11794B4660014176F /* POPMath.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC6465CF1794B4660014176F /* POPMath.mm */; }; + EC67007218D3D89F00F7387F /* POPCGUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = EC67007018D3D89F00F7387F /* POPCGUtils.h */; }; + EC67007318D3D89F00F7387F /* POPCGUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = EC67007018D3D89F00F7387F /* POPCGUtils.h */; }; + EC67007418D3D89F00F7387F /* POPCGUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC67007118D3D89F00F7387F /* POPCGUtils.mm */; }; + EC67007518D3D89F00F7387F /* POPCGUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC67007118D3D89F00F7387F /* POPCGUtils.mm */; }; + EC67007618D3D96500F7387F /* POPCGUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC67007118D3D89F00F7387F /* POPCGUtils.mm */; }; + EC67007718D3D96600F7387F /* POPCGUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC67007118D3D89F00F7387F /* POPCGUtils.mm */; }; + EC68858118C7B60000C6194C /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EC68858018C7B60000C6194C /* Cocoa.framework */; }; + EC68858B18C7B60000C6194C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = EC68858918C7B60000C6194C /* InfoPlist.strings */; }; + EC6885B018C7BD0A00C6194C /* POPAnimatableProperty.h in Headers */ = {isa = PBXBuildFile; fileRef = EC19128E162FB5B700E0CC76 /* POPAnimatableProperty.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC6885B118C7BD1000C6194C /* POPAnimatableProperty.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC19128F162FB5B700E0CC76 /* POPAnimatableProperty.mm */; }; + EC6885B318C7BD1500C6194C /* POPAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = EC191288162FB5B700E0CC76 /* POPAnimation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC6885B418C7BD1A00C6194C /* POPAnimation.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC191289162FB5B700E0CC76 /* POPAnimation.mm */; }; + EC6885B518C7BD1C00C6194C /* POPAnimationEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = EC9997531756A0C300A73F49 /* POPAnimationEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC6885B618C7BD2200C6194C /* POPAnimationEvent.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC9997541756A0C300A73F49 /* POPAnimationEvent.mm */; }; + EC6885B718C7BD2600C6194C /* POPAnimationEventInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = EC9997571756A17B00A73F49 /* POPAnimationEventInternal.h */; }; + EC6885B818C7BD2B00C6194C /* POPAnimationExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = EC0AE12F16BC73CE001DA2CE /* POPAnimationExtras.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC6885B918C7BD3000C6194C /* POPAnimationExtras.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC0AE13016BC73CE001DA2CE /* POPAnimationExtras.mm */; }; + EC6885BA18C7BD3400C6194C /* POPAnimationInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = EC19128A162FB5B700E0CC76 /* POPAnimationInternal.h */; }; + EC6885BB18C7BD3700C6194C /* POPMath.h in Headers */ = {isa = PBXBuildFile; fileRef = EC6465CE1794B4660014176F /* POPMath.h */; }; + EC6885BC18C7BD3A00C6194C /* POPMath.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC6465CF1794B4660014176F /* POPMath.mm */; }; + EC6885BD18C7BD3E00C6194C /* POPAnimationRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = EC95538E1743E278001E6AF2 /* POPAnimationRuntime.h */; }; + EC6885BE18C7BD4000C6194C /* POPAnimationRuntime.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC95538F1743E278001E6AF2 /* POPAnimationRuntime.mm */; }; + EC6885C218C7BD4B00C6194C /* POPAnimator.h in Headers */ = {isa = PBXBuildFile; fileRef = EC19128B162FB5B700E0CC76 /* POPAnimator.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC6885C318C7BD4E00C6194C /* POPAnimator.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC19128C162FB5B700E0CC76 /* POPAnimator.mm */; }; + EC6885C418C7BD5100C6194C /* POPAnimatorPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = EC19128D162FB5B700E0CC76 /* POPAnimatorPrivate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC6885C518C7BD5500C6194C /* POPCustomAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E17BB1E17457345009842B6 /* POPCustomAnimation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC6885C618C7BD5900C6194C /* POPCustomAnimation.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5E17BB1F17457345009842B6 /* POPCustomAnimation.mm */; }; + EC6885C718C7BD5C00C6194C /* POPSpringSolver.h in Headers */ = {isa = PBXBuildFile; fileRef = 90AA30B618988BBE00E3BDF7 /* POPSpringSolver.h */; }; + EC6885C818C7BD5F00C6194C /* POPLayerExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = EC94B07B17D95CAA003CE2C8 /* POPLayerExtras.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC6885C918C7BD6300C6194C /* POPLayerExtras.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC94B07C17D95CAA003CE2C8 /* POPLayerExtras.mm */; }; + EC6885CA18C7BD6500C6194C /* FloatConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = ECCBC57117D96DBD00C69976 /* FloatConversion.h */; }; + EC6885CF18C7BD7B00C6194C /* POPDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = EC91E95F18C00EC90025B8AD /* POPDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC6885D018C7BD7F00C6194C /* POPAction.h in Headers */ = {isa = PBXBuildFile; fileRef = EC91E96D18C014DE0025B8AD /* POPAction.h */; }; + EC6885D118C7BD8500C6194C /* TransformationMatrix.h in Headers */ = {isa = PBXBuildFile; fileRef = EC94B07817D95447003CE2C8 /* TransformationMatrix.h */; }; + EC6885D218C7BD8900C6194C /* TransformationMatrix.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC94B07717D95447003CE2C8 /* TransformationMatrix.cpp */; }; + EC6885D418C7C44E00C6194C /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EC6885D318C7C44E00C6194C /* QuartzCore.framework */; }; + EC70AC4418CCF4FC0067018C /* POPVector.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC70AC4218CCF4FC0067018C /* POPVector.mm */; }; + EC70AC4518CCF4FC0067018C /* POPVector.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC70AC4218CCF4FC0067018C /* POPVector.mm */; }; + EC70AC4618CCF4FC0067018C /* POPVector.h in Headers */ = {isa = PBXBuildFile; fileRef = EC70AC4318CCF4FC0067018C /* POPVector.h */; }; + EC70AC4718CCF4FC0067018C /* POPVector.h in Headers */ = {isa = PBXBuildFile; fileRef = EC70AC4318CCF4FC0067018C /* POPVector.h */; }; + EC72875518E13348006EEE54 /* POPCustomAnimationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC72875418E13348006EEE54 /* POPCustomAnimationTests.mm */; }; + EC72875618E13348006EEE54 /* POPCustomAnimationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC72875418E13348006EEE54 /* POPCustomAnimationTests.mm */; }; + EC7E319A18C93D6500B38170 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EC19122A162FB53A00E0CC76 /* SenTestingKit.framework */; }; + EC7E31A018C93D6500B38170 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = EC7E319E18C93D6500B38170 /* InfoPlist.strings */; }; + EC7E31AB18C9419000B38170 /* POPAnimatable.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC99974A17568DAD00A73F49 /* POPAnimatable.mm */; }; + EC7E31AC18C9419200B38170 /* POPBaseAnimationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC6F55A4175E6641008D995D /* POPBaseAnimationTests.mm */; }; + EC7E31AD18C9419600B38170 /* POPAnimationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC191239162FB53A00E0CC76 /* POPAnimationTests.mm */; }; + EC7E31AE18C9419900B38170 /* POPAnimationMRRTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC3F125916FB728B00922E3A /* POPAnimationMRRTests.mm */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + EC7E31AF18C9419C00B38170 /* POPAnimationTestsExtras.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC3F125C16FB78E800922E3A /* POPAnimationTestsExtras.mm */; }; + EC7E31B018C9419F00B38170 /* POPAnimatablePropertyTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC91D51B16FCC45C00E22B47 /* POPAnimatablePropertyTests.mm */; }; + EC7E31B118C941A200B38170 /* POPDecayAnimationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC6F55A1175E654B008D995D /* POPDecayAnimationTests.mm */; }; + EC7E31B218C941A400B38170 /* POPSpringAnimationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC6F55AA175E6B11008D995D /* POPSpringAnimationTests.mm */; }; + EC7E31B318C941A700B38170 /* POPEaseInEaseOutAnimationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC7D2CFB1795AB3100E50A78 /* POPEaseInEaseOutAnimationTests.mm */; }; + EC7E31B418C9422600B38170 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EC6885D318C7C44E00C6194C /* QuartzCore.framework */; }; + EC7E31B518C9422F00B38170 /* pop.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EC68857F18C7B60000C6194C /* pop.framework */; }; + EC8F014018FFBBD300DF8905 /* POPPropertyAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = EC8F013E18FFBBD300DF8905 /* POPPropertyAnimation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC8F014118FFBBD300DF8905 /* POPPropertyAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = EC8F013E18FFBBD300DF8905 /* POPPropertyAnimation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC8F014618FFBC2D00DF8905 /* POPPropertyAnimationInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = EC8F014418FFBC2D00DF8905 /* POPPropertyAnimationInternal.h */; }; + EC8F014718FFBC2D00DF8905 /* POPPropertyAnimationInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = EC8F014418FFBC2D00DF8905 /* POPPropertyAnimationInternal.h */; }; + EC8F014B18FFBC8200DF8905 /* POPPropertyAnimation.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC8F014A18FFBC8200DF8905 /* POPPropertyAnimation.mm */; }; + EC8F014C18FFBC8200DF8905 /* POPPropertyAnimation.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC8F014A18FFBC8200DF8905 /* POPPropertyAnimation.mm */; }; + EC8F014F18FFBD3E00DF8905 /* POPBasicAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = EC8F014D18FFBD3E00DF8905 /* POPBasicAnimation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC8F015018FFBD3E00DF8905 /* POPBasicAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = EC8F014D18FFBD3E00DF8905 /* POPBasicAnimation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC8F015118FFBD3E00DF8905 /* POPBasicAnimation.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC8F014E18FFBD3E00DF8905 /* POPBasicAnimation.mm */; }; + EC8F015218FFBD3E00DF8905 /* POPBasicAnimation.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC8F014E18FFBD3E00DF8905 /* POPBasicAnimation.mm */; }; + EC8F015518FFBD5600DF8905 /* POPBasicAnimationInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = EC8F015318FFBD5600DF8905 /* POPBasicAnimationInternal.h */; }; + EC8F015618FFBD5600DF8905 /* POPBasicAnimationInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = EC8F015318FFBD5600DF8905 /* POPBasicAnimationInternal.h */; }; + EC8F015C18FFBE8C00DF8905 /* POPDecayAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = EC8F015A18FFBE8C00DF8905 /* POPDecayAnimation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC8F015D18FFBE8C00DF8905 /* POPDecayAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = EC8F015A18FFBE8C00DF8905 /* POPDecayAnimation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC8F015E18FFBE8C00DF8905 /* POPDecayAnimation.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC8F015B18FFBE8C00DF8905 /* POPDecayAnimation.mm */; }; + EC8F015F18FFBE8C00DF8905 /* POPDecayAnimation.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC8F015B18FFBE8C00DF8905 /* POPDecayAnimation.mm */; }; + EC8F016218FFBE9D00DF8905 /* POPDecayAnimationInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = EC8F016018FFBE9D00DF8905 /* POPDecayAnimationInternal.h */; }; + EC8F016318FFBE9D00DF8905 /* POPDecayAnimationInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = EC8F016018FFBE9D00DF8905 /* POPDecayAnimationInternal.h */; }; + EC8F016818FFBEB500DF8905 /* POPSpringAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = EC8F016618FFBEB500DF8905 /* POPSpringAnimation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC8F016918FFBEB500DF8905 /* POPSpringAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = EC8F016618FFBEB500DF8905 /* POPSpringAnimation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC8F016A18FFBEB500DF8905 /* POPSpringAnimation.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC8F016718FFBEB500DF8905 /* POPSpringAnimation.mm */; }; + EC8F016B18FFBEB500DF8905 /* POPSpringAnimation.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC8F016718FFBEB500DF8905 /* POPSpringAnimation.mm */; }; + EC8F016E18FFBEC200DF8905 /* POPSpringAnimationInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = EC8F016C18FFBEC200DF8905 /* POPSpringAnimationInternal.h */; }; + EC8F016F18FFBEC200DF8905 /* POPSpringAnimationInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = EC8F016C18FFBEC200DF8905 /* POPSpringAnimationInternal.h */; }; + EC91E96018C00EC90025B8AD /* POPDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = EC91E95F18C00EC90025B8AD /* POPDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC91E96E18C014DE0025B8AD /* POPAction.h in Headers */ = {isa = PBXBuildFile; fileRef = EC91E96D18C014DE0025B8AD /* POPAction.h */; }; + EC94B07917D95447003CE2C8 /* TransformationMatrix.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC94B07717D95447003CE2C8 /* TransformationMatrix.cpp */; }; + EC94B07A17D95447003CE2C8 /* TransformationMatrix.h in Headers */ = {isa = PBXBuildFile; fileRef = EC94B07817D95447003CE2C8 /* TransformationMatrix.h */; }; + EC94B07D17D95CAA003CE2C8 /* POPLayerExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = EC94B07B17D95CAA003CE2C8 /* POPLayerExtras.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC94B07E17D95CAA003CE2C8 /* POPLayerExtras.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC94B07C17D95CAA003CE2C8 /* POPLayerExtras.mm */; }; + EC9553901743E278001E6AF2 /* POPAnimationRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = EC95538E1743E278001E6AF2 /* POPAnimationRuntime.h */; }; + EC9553911743E278001E6AF2 /* POPAnimationRuntime.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC95538F1743E278001E6AF2 /* POPAnimationRuntime.mm */; }; + EC9997551756A0C300A73F49 /* POPAnimationEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = EC9997531756A0C300A73F49 /* POPAnimationEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC9997561756A0C300A73F49 /* POPAnimationEvent.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC9997541756A0C300A73F49 /* POPAnimationEvent.mm */; }; + EC9997591756A17B00A73F49 /* POPAnimationEventInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = EC9997571756A17B00A73F49 /* POPAnimationEventInternal.h */; }; + ECA0D5C018D8196A003720DF /* UnitBezier.h in Headers */ = {isa = PBXBuildFile; fileRef = ECA0D5BF18D8196A003720DF /* UnitBezier.h */; }; + ECA0D5C118D8196A003720DF /* UnitBezier.h in Headers */ = {isa = PBXBuildFile; fileRef = ECA0D5BF18D8196A003720DF /* UnitBezier.h */; }; + ECA94D0D18ECAE82002E4CEB /* POP.h in Headers */ = {isa = PBXBuildFile; fileRef = ECA94D0B18ECAE82002E4CEB /* POP.h */; settings = {ATTRIBUTES = (Public, ); }; }; + ECA94D0E18ECAE82002E4CEB /* POP.h in Headers */ = {isa = PBXBuildFile; fileRef = ECA94D0B18ECAE82002E4CEB /* POP.h */; settings = {ATTRIBUTES = (Public, ); }; }; + ECCBC57217D96DBD00C69976 /* FloatConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = ECCBC57117D96DBD00C69976 /* FloatConversion.h */; }; + ECD80F1118CFD2EF00AE4303 /* POPGeometry.h in Headers */ = {isa = PBXBuildFile; fileRef = ECD80F0F18CFD2EF00AE4303 /* POPGeometry.h */; settings = {ATTRIBUTES = (Public, ); }; }; + ECD80F1218CFD2EF00AE4303 /* POPGeometry.h in Headers */ = {isa = PBXBuildFile; fileRef = ECD80F0F18CFD2EF00AE4303 /* POPGeometry.h */; settings = {ATTRIBUTES = (Public, ); }; }; + ECD80F1318CFD2EF00AE4303 /* POPGeometry.mm in Sources */ = {isa = PBXBuildFile; fileRef = ECD80F1018CFD2EF00AE4303 /* POPGeometry.mm */; }; + ECD80F1418CFD2EF00AE4303 /* POPGeometry.mm in Sources */ = {isa = PBXBuildFile; fileRef = ECD80F1018CFD2EF00AE4303 /* POPGeometry.mm */; }; + ECDA0CC618C92BC900D14897 /* POPAnimatable.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC99974A17568DAD00A73F49 /* POPAnimatable.mm */; }; + ECDA0CC718C92BD200D14897 /* POPBaseAnimationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC6F55A4175E6641008D995D /* POPBaseAnimationTests.mm */; }; + ECDA0CC818C92BD200D14897 /* POPAnimationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC191239162FB53A00E0CC76 /* POPAnimationTests.mm */; }; + ECDA0CC918C92BD200D14897 /* POPAnimationMRRTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC3F125916FB728B00922E3A /* POPAnimationMRRTests.mm */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + ECDA0CCA18C92BD200D14897 /* POPAnimationTestsExtras.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC3F125C16FB78E800922E3A /* POPAnimationTestsExtras.mm */; }; + ECDA0CCB18C92BD200D14897 /* POPAnimatablePropertyTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC91D51B16FCC45C00E22B47 /* POPAnimatablePropertyTests.mm */; }; + ECDA0CCC18C92BD200D14897 /* POPDecayAnimationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC6F55A1175E654B008D995D /* POPDecayAnimationTests.mm */; }; + ECDA0CCD18C92BD200D14897 /* POPSpringAnimationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC6F55AA175E6B11008D995D /* POPSpringAnimationTests.mm */; }; + ECDA0CCE18C92BD200D14897 /* POPEaseInEaseOutAnimationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = EC7D2CFB1795AB3100E50A78 /* POPEaseInEaseOutAnimationTests.mm */; }; + ECDA0CCF18C92C3E00D14897 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ECC5A887162FBD6200F7F15C /* QuartzCore.framework */; }; + ECDA0CD018C92C4C00D14897 /* libpop.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EC191218162FB53A00E0CC76 /* libpop.a */; }; + ECDA0CD118C92D3900D14897 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ECC5A89D162FBD9B00F7F15C /* CoreGraphics.framework */; }; + ECF01ED418C92B7F009E0AD1 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EC19122A162FB53A00E0CC76 /* SenTestingKit.framework */; }; + ECF01ED518C92B7F009E0AD1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EC19121B162FB53A00E0CC76 /* Foundation.framework */; }; + ECF01ED618C92B7F009E0AD1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ECEED93D18C91ACF00DD95F2 /* UIKit.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + EC7E31A418C93D6600B38170 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = EC19120F162FB53A00E0CC76 /* Project object */; + proxyType = 1; + remoteGlobalIDString = EC68857E18C7B60000C6194C; + remoteInfo = "POP-OSX"; + }; + ECF01EE018C92B80009E0AD1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = EC19120F162FB53A00E0CC76 /* Project object */; + proxyType = 1; + remoteGlobalIDString = EC191217162FB53A00E0CC76; + remoteInfo = POP; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + EC191216162FB53A00E0CC76 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/${PRODUCT_NAME}"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 02E3EF56E92B403A9BF79E26 /* boost */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = archive.ar; name = boost; path = ../../Vendor/Boost/boost.framework/boost; sourceTree = SRCROOT; }; + 403DCBBC1783F4A000793AE1 /* libFBWebP.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libFBWebP.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 5E17BB1E17457345009842B6 /* POPCustomAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPCustomAnimation.h; sourceTree = ""; }; + 5E17BB1F17457345009842B6 /* POPCustomAnimation.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPCustomAnimation.mm; sourceTree = ""; }; + 68569BF4084F464294AFEBA1 /* libFBReachability.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libFBReachability.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 90AA30B618988BBE00E3BDF7 /* POPSpringSolver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPSpringSolver.h; sourceTree = ""; }; + B551F115E86E4C7CA0D92E78 /* libDoubleConversion.a */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = archive.ar; path = libDoubleConversion.a; sourceTree = BUILT_PRODUCTS_DIR; }; + BD1A8D5BCA37415F86519334 /* libCAres.a */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = archive.ar; path = libCAres.a; sourceTree = BUILT_PRODUCTS_DIR; }; + C4E7267217CE4436805A912B /* libFBCoreDataKit.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libFBCoreDataKit.a; sourceTree = BUILT_PRODUCTS_DIR; }; + CD38BFD00B29456D9B40D640 /* Pods-pop-tests.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-pop-tests.xcconfig"; path = "Pods/Pods-pop-tests.xcconfig"; sourceTree = ""; }; + D075ECD10B62441CBB9A90F6 /* libPods-pop-tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-pop-tests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + D411892A480A401FB4D1AA07 /* libPods-pop-tests-osx.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-pop-tests-osx.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + D49ED131870840178CA788F1 /* Pods-pop-tests-osx.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-pop-tests-osx.xcconfig"; path = "Pods/Pods-pop-tests-osx.xcconfig"; sourceTree = ""; }; + D52A06EC505A4B82B3BBD5E6 /* libEvent.a */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = archive.ar; path = libEvent.a; sourceTree = BUILT_PRODUCTS_DIR; }; + E5D894724A104C729754ABCA /* libGLog.a */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = archive.ar; path = libGLog.a; sourceTree = BUILT_PRODUCTS_DIR; }; + EC0AE12F16BC73CE001DA2CE /* POPAnimationExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPAnimationExtras.h; sourceTree = ""; }; + EC0AE13016BC73CE001DA2CE /* POPAnimationExtras.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPAnimationExtras.mm; sourceTree = ""; }; + EC191218162FB53A00E0CC76 /* libpop.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libpop.a; sourceTree = BUILT_PRODUCTS_DIR; }; + EC19121B162FB53A00E0CC76 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + EC19121F162FB53A00E0CC76 /* POP-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "POP-Prefix.pch"; path = "../pop/POP-Prefix.pch"; sourceTree = ""; }; + EC19122A162FB53A00E0CC76 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; + EC19122C162FB53A00E0CC76 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + EC191239162FB53A00E0CC76 /* POPAnimationTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = POPAnimationTests.mm; sourceTree = ""; }; + EC191288162FB5B700E0CC76 /* POPAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPAnimation.h; sourceTree = ""; }; + EC191289162FB5B700E0CC76 /* POPAnimation.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPAnimation.mm; sourceTree = ""; }; + EC19128A162FB5B700E0CC76 /* POPAnimationInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPAnimationInternal.h; sourceTree = ""; }; + EC19128B162FB5B700E0CC76 /* POPAnimator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPAnimator.h; sourceTree = ""; }; + EC19128C162FB5B700E0CC76 /* POPAnimator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPAnimator.mm; sourceTree = ""; }; + EC19128D162FB5B700E0CC76 /* POPAnimatorPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPAnimatorPrivate.h; sourceTree = ""; }; + EC19128E162FB5B700E0CC76 /* POPAnimatableProperty.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPAnimatableProperty.h; sourceTree = ""; }; + EC19128F162FB5B700E0CC76 /* POPAnimatableProperty.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPAnimatableProperty.mm; sourceTree = ""; }; + EC1CD94E18D80A5C00DE2649 /* POPAnimationPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPAnimationPrivate.h; sourceTree = ""; }; + EC35DB2618EE3E820023E077 /* POPAnimationTracer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPAnimationTracer.h; sourceTree = ""; }; + EC35DB2718EE3E820023E077 /* POPAnimationTracer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPAnimationTracer.mm; sourceTree = ""; }; + EC35DB2818EE3E820023E077 /* POPAnimationTracerInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPAnimationTracerInternal.h; sourceTree = ""; }; + EC3F125916FB728B00922E3A /* POPAnimationMRRTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPAnimationMRRTests.mm; sourceTree = ""; }; + EC3F125B16FB78E800922E3A /* POPAnimationTestsExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPAnimationTestsExtras.h; sourceTree = ""; }; + EC3F125C16FB78E800922E3A /* POPAnimationTestsExtras.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPAnimationTestsExtras.mm; sourceTree = ""; }; + EC6465CE1794B4660014176F /* POPMath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPMath.h; sourceTree = ""; }; + EC6465CF1794B4660014176F /* POPMath.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPMath.mm; sourceTree = ""; }; + EC67007018D3D89F00F7387F /* POPCGUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPCGUtils.h; sourceTree = ""; }; + EC67007118D3D89F00F7387F /* POPCGUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPCGUtils.mm; sourceTree = ""; }; + EC68857F18C7B60000C6194C /* pop.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = pop.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EC68858018C7B60000C6194C /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = Library/Frameworks/Cocoa.framework; sourceTree = DEVELOPER_DIR; }; + EC68858318C7B60000C6194C /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + EC68858418C7B60000C6194C /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; + EC68858518C7B60000C6194C /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; + EC68858818C7B60000C6194C /* pop-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "pop-Info.plist"; sourceTree = ""; }; + EC68858A18C7B60000C6194C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + EC68858C18C7B60000C6194C /* pop-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "pop-Prefix.pch"; sourceTree = ""; }; + EC68859518C7B60100C6194C /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + EC6885D318C7C44E00C6194C /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/System/Library/Frameworks/QuartzCore.framework; sourceTree = DEVELOPER_DIR; }; + EC6F55A1175E654B008D995D /* POPDecayAnimationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPDecayAnimationTests.mm; sourceTree = ""; }; + EC6F55A3175E6641008D995D /* POPBaseAnimationTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPBaseAnimationTests.h; sourceTree = ""; }; + EC6F55A4175E6641008D995D /* POPBaseAnimationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPBaseAnimationTests.mm; sourceTree = ""; }; + EC6F55AA175E6B11008D995D /* POPSpringAnimationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPSpringAnimationTests.mm; sourceTree = ""; }; + EC70AC4218CCF4FC0067018C /* POPVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPVector.mm; sourceTree = ""; }; + EC70AC4318CCF4FC0067018C /* POPVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPVector.h; sourceTree = ""; }; + EC72875418E13348006EEE54 /* POPCustomAnimationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPCustomAnimationTests.mm; sourceTree = ""; }; + EC7D2CFB1795AB3100E50A78 /* POPEaseInEaseOutAnimationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPEaseInEaseOutAnimationTests.mm; sourceTree = ""; }; + EC7E319918C93D6500B38170 /* pop-tests-osx.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "pop-tests-osx.octest"; sourceTree = BUILT_PRODUCTS_DIR; }; + EC7E319D18C93D6500B38170 /* pop-tests-osx-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "pop-tests-osx-Info.plist"; sourceTree = ""; }; + EC7E319F18C93D6500B38170 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + EC7E31A318C93D6500B38170 /* pop-tests-osx-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "pop-tests-osx-Prefix.pch"; sourceTree = ""; }; + EC882A7718C91983007829CC /* pop-tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "pop-tests-Info.plist"; sourceTree = ""; }; + EC882A7918C91983007829CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + EC882A7D18C91983007829CC /* pop-tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "pop-tests-Prefix.pch"; sourceTree = ""; }; + EC8F013E18FFBBD300DF8905 /* POPPropertyAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPPropertyAnimation.h; sourceTree = ""; }; + EC8F014418FFBC2D00DF8905 /* POPPropertyAnimationInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPPropertyAnimationInternal.h; sourceTree = ""; }; + EC8F014A18FFBC8200DF8905 /* POPPropertyAnimation.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPPropertyAnimation.mm; sourceTree = ""; }; + EC8F014D18FFBD3E00DF8905 /* POPBasicAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPBasicAnimation.h; sourceTree = ""; }; + EC8F014E18FFBD3E00DF8905 /* POPBasicAnimation.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPBasicAnimation.mm; sourceTree = ""; }; + EC8F015318FFBD5600DF8905 /* POPBasicAnimationInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPBasicAnimationInternal.h; sourceTree = ""; }; + EC8F015A18FFBE8C00DF8905 /* POPDecayAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPDecayAnimation.h; sourceTree = ""; }; + EC8F015B18FFBE8C00DF8905 /* POPDecayAnimation.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPDecayAnimation.mm; sourceTree = ""; }; + EC8F016018FFBE9D00DF8905 /* POPDecayAnimationInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPDecayAnimationInternal.h; sourceTree = ""; }; + EC8F016618FFBEB500DF8905 /* POPSpringAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPSpringAnimation.h; sourceTree = ""; }; + EC8F016718FFBEB500DF8905 /* POPSpringAnimation.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPSpringAnimation.mm; sourceTree = ""; }; + EC8F016C18FFBEC200DF8905 /* POPSpringAnimationInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPSpringAnimationInternal.h; sourceTree = ""; }; + EC91D51B16FCC45C00E22B47 /* POPAnimatablePropertyTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPAnimatablePropertyTests.mm; sourceTree = ""; }; + EC91E95F18C00EC90025B8AD /* POPDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPDefines.h; sourceTree = ""; }; + EC91E96D18C014DE0025B8AD /* POPAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPAction.h; sourceTree = ""; }; + EC94B07717D95447003CE2C8 /* TransformationMatrix.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = TransformationMatrix.cpp; path = WebCore/TransformationMatrix.cpp; sourceTree = ""; }; + EC94B07817D95447003CE2C8 /* TransformationMatrix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TransformationMatrix.h; path = WebCore/TransformationMatrix.h; sourceTree = ""; }; + EC94B07B17D95CAA003CE2C8 /* POPLayerExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPLayerExtras.h; sourceTree = ""; }; + EC94B07C17D95CAA003CE2C8 /* POPLayerExtras.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPLayerExtras.mm; sourceTree = ""; }; + EC95538E1743E278001E6AF2 /* POPAnimationRuntime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPAnimationRuntime.h; sourceTree = ""; }; + EC95538F1743E278001E6AF2 /* POPAnimationRuntime.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPAnimationRuntime.mm; sourceTree = ""; }; + EC99974917568DAD00A73F49 /* POPAnimatable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = POPAnimatable.h; path = "pop-tests/POPAnimatable.h"; sourceTree = SOURCE_ROOT; }; + EC99974A17568DAD00A73F49 /* POPAnimatable.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = POPAnimatable.mm; path = "pop-tests/POPAnimatable.mm"; sourceTree = SOURCE_ROOT; }; + EC9997531756A0C300A73F49 /* POPAnimationEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPAnimationEvent.h; sourceTree = ""; }; + EC9997541756A0C300A73F49 /* POPAnimationEvent.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPAnimationEvent.mm; sourceTree = ""; }; + EC9997571756A17B00A73F49 /* POPAnimationEventInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPAnimationEventInternal.h; sourceTree = ""; }; + ECA0D5BF18D8196A003720DF /* UnitBezier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = UnitBezier.h; path = WebCore/UnitBezier.h; sourceTree = ""; }; + ECA94D0B18ECAE82002E4CEB /* POP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = POP.h; path = pop/POP.h; sourceTree = SOURCE_ROOT; }; + ECADA2A416A8A99E00A20182 /* libMessagePack.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libMessagePack.a; sourceTree = BUILT_PRODUCTS_DIR; }; + ECADA2A516A8A99E00A20182 /* libSPDY.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libSPDY.a; sourceTree = BUILT_PRODUCTS_DIR; }; + ECC1DB0918CA291B008C7DEA /* Application-iOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Application-iOS.xcconfig"; sourceTree = ""; }; + ECC1DB0A18CA291B008C7DEA /* Application-OSX.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Application-OSX.xcconfig"; sourceTree = ""; }; + ECC1DB0B18CA291B008C7DEA /* Base-iOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Base-iOS.xcconfig"; sourceTree = ""; }; + ECC1DB0C18CA291B008C7DEA /* Base-OSX.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Base-OSX.xcconfig"; sourceTree = ""; }; + ECC1DB0E18CA291B008C7DEA /* Compiler.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Compiler.xcconfig; sourceTree = ""; }; + ECC1DB0F18CA291B008C7DEA /* iOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = iOS.xcconfig; sourceTree = ""; }; + ECC1DB1018CA291B008C7DEA /* OSX.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = OSX.xcconfig; sourceTree = ""; }; + ECC1DB1218CA291B008C7DEA /* Project-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Debug.xcconfig"; sourceTree = ""; }; + ECC1DB1318CA291B008C7DEA /* Project-GCOV.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-GCOV.xcconfig"; sourceTree = ""; }; + ECC1DB1618CA291B008C7DEA /* Project-Profile.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Profile.xcconfig"; sourceTree = ""; }; + ECC1DB1718CA291B008C7DEA /* Project-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Release.xcconfig"; sourceTree = ""; }; + ECC1DB1818CA291B008C7DEA /* Project.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Project.xcconfig; sourceTree = ""; }; + ECC1DB1A18CA291B008C7DEA /* StaticLibrary-iOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StaticLibrary-iOS.xcconfig"; sourceTree = ""; }; + ECC1DB1B18CA291B008C7DEA /* StaticLibrary-OSX.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StaticLibrary-OSX.xcconfig"; sourceTree = ""; }; + ECC1DB1D18CA291B008C7DEA /* ApplicationTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ApplicationTests.xcconfig; sourceTree = ""; }; + ECC1DB1E18CA291B008C7DEA /* LogicTests-iOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "LogicTests-iOS.xcconfig"; sourceTree = ""; }; + ECC1DB1F18CA291B008C7DEA /* LogicTests-OSX.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "LogicTests-OSX.xcconfig"; sourceTree = ""; }; + ECC5A887162FBD6200F7F15C /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + ECC5A889162FBD7600F7F15C /* libFBAnalytics.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libFBAnalytics.a; sourceTree = BUILT_PRODUCTS_DIR; }; + ECC5A88B162FBD7600F7F15C /* libFBFoundation.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libFBFoundation.a; sourceTree = BUILT_PRODUCTS_DIR; }; + ECC5A88C162FBD7600F7F15C /* libFBProvider.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libFBProvider.a; sourceTree = BUILT_PRODUCTS_DIR; }; + ECC5A88D162FBD7600F7F15C /* libFBTest.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libFBTest.a; sourceTree = BUILT_PRODUCTS_DIR; }; + ECC5A88F162FBD7600F7F15C /* libOCMock.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libOCMock.a; sourceTree = BUILT_PRODUCTS_DIR; }; + ECC5A897162FBD8700F7F15C /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; + ECC5A899162FBD8E00F7F15C /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; + ECC5A89B162FBD9500F7F15C /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; + ECC5A89D162FBD9B00F7F15C /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + ECC5A89F162FBDA400F7F15C /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; + ECC5A8A1162FBDAB00F7F15C /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; + ECC5A8A3162FBDC400F7F15C /* Accounts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accounts.framework; path = System/Library/Frameworks/Accounts.framework; sourceTree = SDKROOT; }; + ECC5A8A5162FBDCA00F7F15C /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; + ECC5A8A7162FBDCF00F7F15C /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; + ECC5A8A9162FBDD800F7F15C /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; + ECC5A8AB162FBDE100F7F15C /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = System/Library/Frameworks/ImageIO.framework; sourceTree = SDKROOT; }; + ECC5A8AD162FBDEB00F7F15C /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; }; + ECC5A8AF162FBDF600F7F15C /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; }; + ECC5A8B1162FBE0400F7F15C /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; + ECC5A8B3162FBE0900F7F15C /* libicucore.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libicucore.dylib; path = usr/lib/libicucore.dylib; sourceTree = SDKROOT; }; + ECC5A8B5162FBE1600F7F15C /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; + ECC5A8B7162FBE1B00F7F15C /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + ECC5A8BA162FBF3A00F7F15C /* AdSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdSupport.framework; path = System/Library/Frameworks/AdSupport.framework; sourceTree = SDKROOT; }; + ECC63411C31441ECAC3F5559 /* libLiger.a */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = archive.ar; path = libLiger.a; sourceTree = BUILT_PRODUCTS_DIR; }; + ECCBC57117D96DBD00C69976 /* FloatConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FloatConversion.h; path = WebCore/FloatConversion.h; sourceTree = ""; }; + ECD80F0F18CFD2EF00AE4303 /* POPGeometry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = POPGeometry.h; sourceTree = ""; }; + ECD80F1018CFD2EF00AE4303 /* POPGeometry.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = POPGeometry.mm; sourceTree = ""; }; + ECED421816F7D1FD00FFBEAC /* libFBNetworker.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libFBNetworker.a; sourceTree = BUILT_PRODUCTS_DIR; }; + ECEED93A18C91AB600DD95F2 /* libOCMock.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libOCMock.a; sourceTree = BUILT_PRODUCTS_DIR; }; + ECEED93D18C91ACF00DD95F2 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + ECF01ED318C92B7F009E0AD1 /* pop-tests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "pop-tests.octest"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + EC191215162FB53A00E0CC76 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EC19121C162FB53A00E0CC76 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EC68857B18C7B60000C6194C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EC6885D418C7C44E00C6194C /* QuartzCore.framework in Frameworks */, + EC68858118C7B60000C6194C /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EC7E319618C93D6500B38170 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EC7E31B518C9422F00B38170 /* pop.framework in Frameworks */, + EC7E31B418C9422600B38170 /* QuartzCore.framework in Frameworks */, + EC7E319A18C93D6500B38170 /* SenTestingKit.framework in Frameworks */, + ADE1BB0636F940EC8EA7EE57 /* libPods-pop-tests-osx.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + ECF01ED018C92B7F009E0AD1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ECDA0CD118C92D3900D14897 /* CoreGraphics.framework in Frameworks */, + ECDA0CD018C92C4C00D14897 /* libpop.a in Frameworks */, + ECDA0CCF18C92C3E00D14897 /* QuartzCore.framework in Frameworks */, + ECF01ED418C92B7F009E0AD1 /* SenTestingKit.framework in Frameworks */, + ECF01ED618C92B7F009E0AD1 /* UIKit.framework in Frameworks */, + ECF01ED518C92B7F009E0AD1 /* Foundation.framework in Frameworks */, + BFD8EB4ECB184EF99C22123A /* libPods-pop-tests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + EC19120D162FB53A00E0CC76 = { + isa = PBXGroup; + children = ( + EC19121D162FB53A00E0CC76 /* pop */, + EC882A7518C91983007829CC /* pop-tests */, + ECC1DB0718CA291B008C7DEA /* Configuration */, + EC19121A162FB53A00E0CC76 /* Frameworks */, + EC191219162FB53A00E0CC76 /* Products */, + CD38BFD00B29456D9B40D640 /* Pods-pop-tests.xcconfig */, + D49ED131870840178CA788F1 /* Pods-pop-tests-osx.xcconfig */, + ); + sourceTree = ""; + }; + EC191219162FB53A00E0CC76 /* Products */ = { + isa = PBXGroup; + children = ( + EC191218162FB53A00E0CC76 /* libpop.a */, + EC68857F18C7B60000C6194C /* pop.framework */, + ECF01ED318C92B7F009E0AD1 /* pop-tests.octest */, + EC7E319918C93D6500B38170 /* pop-tests-osx.octest */, + ); + name = Products; + sourceTree = ""; + }; + EC19121A162FB53A00E0CC76 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ECEED93D18C91ACF00DD95F2 /* UIKit.framework */, + ECEED93A18C91AB600DD95F2 /* libOCMock.a */, + EC6885D318C7C44E00C6194C /* QuartzCore.framework */, + 403DCBBC1783F4A000793AE1 /* libFBWebP.a */, + ECED421816F7D1FD00FFBEAC /* libFBNetworker.a */, + 68569BF4084F464294AFEBA1 /* libFBReachability.a */, + ECADA2A416A8A99E00A20182 /* libMessagePack.a */, + ECADA2A516A8A99E00A20182 /* libSPDY.a */, + ECC5A8BA162FBF3A00F7F15C /* AdSupport.framework */, + ECC5A8B7162FBE1B00F7F15C /* Security.framework */, + ECC5A8B5162FBE1600F7F15C /* AssetsLibrary.framework */, + ECC5A8B3162FBE0900F7F15C /* libicucore.dylib */, + ECC5A8B1162FBE0400F7F15C /* libz.dylib */, + ECC5A8AF162FBDF600F7F15C /* CoreTelephony.framework */, + ECC5A8AD162FBDEB00F7F15C /* CoreText.framework */, + ECC5A8AB162FBDE100F7F15C /* ImageIO.framework */, + ECC5A8A9162FBDD800F7F15C /* SystemConfiguration.framework */, + ECC5A8A7162FBDCF00F7F15C /* CoreLocation.framework */, + ECC5A8A5162FBDCA00F7F15C /* AVFoundation.framework */, + ECC5A8A3162FBDC400F7F15C /* Accounts.framework */, + ECC5A8A1162FBDAB00F7F15C /* MobileCoreServices.framework */, + ECC5A89F162FBDA400F7F15C /* CoreData.framework */, + ECC5A89D162FBD9B00F7F15C /* CoreGraphics.framework */, + ECC5A89B162FBD9500F7F15C /* AudioToolbox.framework */, + ECC5A899162FBD8E00F7F15C /* CFNetwork.framework */, + ECC5A897162FBD8700F7F15C /* AddressBook.framework */, + ECC5A889162FBD7600F7F15C /* libFBAnalytics.a */, + ECC5A88B162FBD7600F7F15C /* libFBFoundation.a */, + C4E7267217CE4436805A912B /* libFBCoreDataKit.a */, + ECC5A88C162FBD7600F7F15C /* libFBProvider.a */, + ECC5A88D162FBD7600F7F15C /* libFBTest.a */, + ECC5A88F162FBD7600F7F15C /* libOCMock.a */, + ECC5A887162FBD6200F7F15C /* QuartzCore.framework */, + EC19121B162FB53A00E0CC76 /* Foundation.framework */, + EC19122A162FB53A00E0CC76 /* SenTestingKit.framework */, + EC19122C162FB53A00E0CC76 /* UIKit.framework */, + E5D894724A104C729754ABCA /* libGLog.a */, + B551F115E86E4C7CA0D92E78 /* libDoubleConversion.a */, + D52A06EC505A4B82B3BBD5E6 /* libEvent.a */, + BD1A8D5BCA37415F86519334 /* libCAres.a */, + ECC63411C31441ECAC3F5559 /* libLiger.a */, + 02E3EF56E92B403A9BF79E26 /* boost */, + EC68858018C7B60000C6194C /* Cocoa.framework */, + EC68859518C7B60100C6194C /* XCTest.framework */, + EC68858218C7B60000C6194C /* Other Frameworks */, + D075ECD10B62441CBB9A90F6 /* libPods-pop-tests.a */, + D411892A480A401FB4D1AA07 /* libPods-pop-tests-osx.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + EC19121D162FB53A00E0CC76 /* pop */ = { + isa = PBXGroup; + children = ( + EC8F015918FFBD7A00DF8905 /* Animations */, + EC8F017318FFC4CB00DF8905 /* Engine */, + EC8F017218FFC44800DF8905 /* Utility */, + ECA0D5BE18D818C3003720DF /* WebCore */, + EC6885AF18C7BCA400C6194C /* iOS Supporting Files */, + EC68858618C7B60000C6194C /* OSX Supporting Files */, + ECA94D0B18ECAE82002E4CEB /* POP.h */, + ); + path = pop; + sourceTree = ""; + }; + EC68858218C7B60000C6194C /* Other Frameworks */ = { + isa = PBXGroup; + children = ( + EC68858318C7B60000C6194C /* Foundation.framework */, + EC68858418C7B60000C6194C /* CoreData.framework */, + EC68858518C7B60000C6194C /* AppKit.framework */, + ); + name = "Other Frameworks"; + sourceTree = ""; + }; + EC68858618C7B60000C6194C /* OSX Supporting Files */ = { + isa = PBXGroup; + children = ( + EC68858818C7B60000C6194C /* pop-Info.plist */, + EC68858918C7B60000C6194C /* InfoPlist.strings */, + EC68858C18C7B60000C6194C /* pop-Prefix.pch */, + ); + name = "OSX Supporting Files"; + sourceTree = ""; + }; + EC6885AF18C7BCA400C6194C /* iOS Supporting Files */ = { + isa = PBXGroup; + children = ( + EC19121F162FB53A00E0CC76 /* POP-Prefix.pch */, + ); + name = "iOS Supporting Files"; + path = "pop-tests"; + sourceTree = SOURCE_ROOT; + }; + EC7E319C18C93D6500B38170 /* Supporting Files - OSX */ = { + isa = PBXGroup; + children = ( + EC7E319D18C93D6500B38170 /* pop-tests-osx-Info.plist */, + EC7E319E18C93D6500B38170 /* InfoPlist.strings */, + EC7E31A318C93D6500B38170 /* pop-tests-osx-Prefix.pch */, + ); + name = "Supporting Files - OSX"; + path = "pop-tests-osx"; + sourceTree = SOURCE_ROOT; + }; + EC882A7518C91983007829CC /* pop-tests */ = { + isa = PBXGroup; + children = ( + EC99974917568DAD00A73F49 /* POPAnimatable.h */, + EC99974A17568DAD00A73F49 /* POPAnimatable.mm */, + EC6F55A3175E6641008D995D /* POPBaseAnimationTests.h */, + EC6F55A4175E6641008D995D /* POPBaseAnimationTests.mm */, + EC191239162FB53A00E0CC76 /* POPAnimationTests.mm */, + EC3F125916FB728B00922E3A /* POPAnimationMRRTests.mm */, + EC3F125B16FB78E800922E3A /* POPAnimationTestsExtras.h */, + EC3F125C16FB78E800922E3A /* POPAnimationTestsExtras.mm */, + EC91D51B16FCC45C00E22B47 /* POPAnimatablePropertyTests.mm */, + EC6F55A1175E654B008D995D /* POPDecayAnimationTests.mm */, + EC6F55AA175E6B11008D995D /* POPSpringAnimationTests.mm */, + EC7D2CFB1795AB3100E50A78 /* POPEaseInEaseOutAnimationTests.mm */, + EC72875418E13348006EEE54 /* POPCustomAnimationTests.mm */, + EC7E319C18C93D6500B38170 /* Supporting Files - OSX */, + EC882A7618C91983007829CC /* Supporting Files - iOS */, + ); + path = "pop-tests"; + sourceTree = ""; + }; + EC882A7618C91983007829CC /* Supporting Files - iOS */ = { + isa = PBXGroup; + children = ( + EC882A7718C91983007829CC /* pop-tests-Info.plist */, + EC882A7818C91983007829CC /* InfoPlist.strings */, + EC882A7D18C91983007829CC /* pop-tests-Prefix.pch */, + ); + name = "Supporting Files - iOS"; + sourceTree = ""; + }; + EC8F015918FFBD7A00DF8905 /* Animations */ = { + isa = PBXGroup; + children = ( + EC191288162FB5B700E0CC76 /* POPAnimation.h */, + EC191289162FB5B700E0CC76 /* POPAnimation.mm */, + EC19128A162FB5B700E0CC76 /* POPAnimationInternal.h */, + EC1CD94E18D80A5C00DE2649 /* POPAnimationPrivate.h */, + EC8F013E18FFBBD300DF8905 /* POPPropertyAnimation.h */, + EC8F014A18FFBC8200DF8905 /* POPPropertyAnimation.mm */, + EC8F014418FFBC2D00DF8905 /* POPPropertyAnimationInternal.h */, + EC8F014D18FFBD3E00DF8905 /* POPBasicAnimation.h */, + EC8F014E18FFBD3E00DF8905 /* POPBasicAnimation.mm */, + EC8F015318FFBD5600DF8905 /* POPBasicAnimationInternal.h */, + 5E17BB1E17457345009842B6 /* POPCustomAnimation.h */, + 5E17BB1F17457345009842B6 /* POPCustomAnimation.mm */, + EC8F015A18FFBE8C00DF8905 /* POPDecayAnimation.h */, + EC8F015B18FFBE8C00DF8905 /* POPDecayAnimation.mm */, + EC8F016018FFBE9D00DF8905 /* POPDecayAnimationInternal.h */, + EC8F016618FFBEB500DF8905 /* POPSpringAnimation.h */, + EC8F016718FFBEB500DF8905 /* POPSpringAnimation.mm */, + EC8F016C18FFBEC200DF8905 /* POPSpringAnimationInternal.h */, + ); + name = Animations; + sourceTree = ""; + }; + EC8F017218FFC44800DF8905 /* Utility */ = { + isa = PBXGroup; + children = ( + EC91E96D18C014DE0025B8AD /* POPAction.h */, + EC67007018D3D89F00F7387F /* POPCGUtils.h */, + EC67007118D3D89F00F7387F /* POPCGUtils.mm */, + EC91E95F18C00EC90025B8AD /* POPDefines.h */, + ECD80F0F18CFD2EF00AE4303 /* POPGeometry.h */, + ECD80F1018CFD2EF00AE4303 /* POPGeometry.mm */, + EC94B07B17D95CAA003CE2C8 /* POPLayerExtras.h */, + EC94B07C17D95CAA003CE2C8 /* POPLayerExtras.mm */, + EC6465CE1794B4660014176F /* POPMath.h */, + EC6465CF1794B4660014176F /* POPMath.mm */, + 90AA30B618988BBE00E3BDF7 /* POPSpringSolver.h */, + EC70AC4318CCF4FC0067018C /* POPVector.h */, + EC70AC4218CCF4FC0067018C /* POPVector.mm */, + ); + name = Utility; + sourceTree = ""; + }; + EC8F017318FFC4CB00DF8905 /* Engine */ = { + isa = PBXGroup; + children = ( + EC19128E162FB5B700E0CC76 /* POPAnimatableProperty.h */, + EC19128F162FB5B700E0CC76 /* POPAnimatableProperty.mm */, + EC9997531756A0C300A73F49 /* POPAnimationEvent.h */, + EC9997541756A0C300A73F49 /* POPAnimationEvent.mm */, + EC9997571756A17B00A73F49 /* POPAnimationEventInternal.h */, + EC0AE12F16BC73CE001DA2CE /* POPAnimationExtras.h */, + EC0AE13016BC73CE001DA2CE /* POPAnimationExtras.mm */, + EC95538E1743E278001E6AF2 /* POPAnimationRuntime.h */, + EC95538F1743E278001E6AF2 /* POPAnimationRuntime.mm */, + EC35DB2618EE3E820023E077 /* POPAnimationTracer.h */, + EC35DB2718EE3E820023E077 /* POPAnimationTracer.mm */, + EC35DB2818EE3E820023E077 /* POPAnimationTracerInternal.h */, + EC19128B162FB5B700E0CC76 /* POPAnimator.h */, + EC19128C162FB5B700E0CC76 /* POPAnimator.mm */, + EC19128D162FB5B700E0CC76 /* POPAnimatorPrivate.h */, + ); + name = Engine; + sourceTree = ""; + }; + ECA0D5BE18D818C3003720DF /* WebCore */ = { + isa = PBXGroup; + children = ( + ECCBC57117D96DBD00C69976 /* FloatConversion.h */, + EC94B07817D95447003CE2C8 /* TransformationMatrix.h */, + EC94B07717D95447003CE2C8 /* TransformationMatrix.cpp */, + ECA0D5BF18D8196A003720DF /* UnitBezier.h */, + ); + name = WebCore; + sourceTree = ""; + }; + ECC1DB0718CA291B008C7DEA /* Configuration */ = { + isa = PBXGroup; + children = ( + ECC1DB0818CA291B008C7DEA /* Applications */, + ECC1DB0B18CA291B008C7DEA /* Base-iOS.xcconfig */, + ECC1DB0C18CA291B008C7DEA /* Base-OSX.xcconfig */, + ECC1DB0D18CA291B008C7DEA /* Platform */, + ECC1DB1118CA291B008C7DEA /* Projects */, + ECC1DB1918CA291B008C7DEA /* Static Libraries */, + ECC1DB1C18CA291B008C7DEA /* Tests */, + ); + path = Configuration; + sourceTree = ""; + }; + ECC1DB0818CA291B008C7DEA /* Applications */ = { + isa = PBXGroup; + children = ( + ECC1DB0918CA291B008C7DEA /* Application-iOS.xcconfig */, + ECC1DB0A18CA291B008C7DEA /* Application-OSX.xcconfig */, + ); + path = Applications; + sourceTree = ""; + }; + ECC1DB0D18CA291B008C7DEA /* Platform */ = { + isa = PBXGroup; + children = ( + ECC1DB0E18CA291B008C7DEA /* Compiler.xcconfig */, + ECC1DB0F18CA291B008C7DEA /* iOS.xcconfig */, + ECC1DB1018CA291B008C7DEA /* OSX.xcconfig */, + ); + path = Platform; + sourceTree = ""; + }; + ECC1DB1118CA291B008C7DEA /* Projects */ = { + isa = PBXGroup; + children = ( + ECC1DB1218CA291B008C7DEA /* Project-Debug.xcconfig */, + ECC1DB1318CA291B008C7DEA /* Project-GCOV.xcconfig */, + ECC1DB1618CA291B008C7DEA /* Project-Profile.xcconfig */, + ECC1DB1718CA291B008C7DEA /* Project-Release.xcconfig */, + ECC1DB1818CA291B008C7DEA /* Project.xcconfig */, + ); + path = Projects; + sourceTree = ""; + }; + ECC1DB1918CA291B008C7DEA /* Static Libraries */ = { + isa = PBXGroup; + children = ( + ECC1DB1A18CA291B008C7DEA /* StaticLibrary-iOS.xcconfig */, + ECC1DB1B18CA291B008C7DEA /* StaticLibrary-OSX.xcconfig */, + ); + path = "Static Libraries"; + sourceTree = ""; + }; + ECC1DB1C18CA291B008C7DEA /* Tests */ = { + isa = PBXGroup; + children = ( + ECC1DB1D18CA291B008C7DEA /* ApplicationTests.xcconfig */, + ECC1DB1E18CA291B008C7DEA /* LogicTests-iOS.xcconfig */, + ECC1DB1F18CA291B008C7DEA /* LogicTests-OSX.xcconfig */, + ); + path = Tests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + EC191294162FB5DD00E0CC76 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ECD80F1118CFD2EF00AE4303 /* POPGeometry.h in Headers */, + EC1CD95018D80A5C00DE2649 /* POPAnimationPrivate.h in Headers */, + EC191295162FB5EC00E0CC76 /* POPAnimation.h in Headers */, + EC191297162FB5EC00E0CC76 /* POPAnimator.h in Headers */, + EC35DB2D18EE3E820023E077 /* POPAnimationTracerInternal.h in Headers */, + EC191299162FB5EC00E0CC76 /* POPAnimatableProperty.h in Headers */, + EC8F014F18FFBD3E00DF8905 /* POPBasicAnimation.h in Headers */, + EC8F014018FFBBD300DF8905 /* POPPropertyAnimation.h in Headers */, + EC191296162FB5EC00E0CC76 /* POPAnimationInternal.h in Headers */, + EC191298162FB5EC00E0CC76 /* POPAnimatorPrivate.h in Headers */, + ECCBC57217D96DBD00C69976 /* FloatConversion.h in Headers */, + EC94B07D17D95CAA003CE2C8 /* POPLayerExtras.h in Headers */, + EC8F016818FFBEB500DF8905 /* POPSpringAnimation.h in Headers */, + 5E17BB2017457345009842B6 /* POPCustomAnimation.h in Headers */, + EC67007218D3D89F00F7387F /* POPCGUtils.h in Headers */, + EC8F016218FFBE9D00DF8905 /* POPDecayAnimationInternal.h in Headers */, + EC0AE13116BC73CE001DA2CE /* POPAnimationExtras.h in Headers */, + EC91E96E18C014DE0025B8AD /* POPAction.h in Headers */, + 90AA30B718988BBE00E3BDF7 /* POPSpringSolver.h in Headers */, + EC8F014618FFBC2D00DF8905 /* POPPropertyAnimationInternal.h in Headers */, + EC8F015C18FFBE8C00DF8905 /* POPDecayAnimation.h in Headers */, + EC91E96018C00EC90025B8AD /* POPDefines.h in Headers */, + EC70AC4618CCF4FC0067018C /* POPVector.h in Headers */, + EC9553901743E278001E6AF2 /* POPAnimationRuntime.h in Headers */, + EC6465D01794B4660014176F /* POPMath.h in Headers */, + EC8F016E18FFBEC200DF8905 /* POPSpringAnimationInternal.h in Headers */, + EC9997551756A0C300A73F49 /* POPAnimationEvent.h in Headers */, + ECA94D0D18ECAE82002E4CEB /* POP.h in Headers */, + EC9997591756A17B00A73F49 /* POPAnimationEventInternal.h in Headers */, + EC94B07A17D95447003CE2C8 /* TransformationMatrix.h in Headers */, + EC8F015518FFBD5600DF8905 /* POPBasicAnimationInternal.h in Headers */, + EC35DB2918EE3E820023E077 /* POPAnimationTracer.h in Headers */, + ECA0D5C018D8196A003720DF /* UnitBezier.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EC68857C18C7B60000C6194C /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ECD80F1218CFD2EF00AE4303 /* POPGeometry.h in Headers */, + EC1CD95118D80A5C00DE2649 /* POPAnimationPrivate.h in Headers */, + EC6885D118C7BD8500C6194C /* TransformationMatrix.h in Headers */, + EC6885B718C7BD2600C6194C /* POPAnimationEventInternal.h in Headers */, + EC35DB2E18EE3E820023E077 /* POPAnimationTracerInternal.h in Headers */, + EC6885C818C7BD5F00C6194C /* POPLayerExtras.h in Headers */, + EC8F015018FFBD3E00DF8905 /* POPBasicAnimation.h in Headers */, + EC8F014118FFBBD300DF8905 /* POPPropertyAnimation.h in Headers */, + EC6885BB18C7BD3700C6194C /* POPMath.h in Headers */, + EC6885C418C7BD5100C6194C /* POPAnimatorPrivate.h in Headers */, + EC6885C518C7BD5500C6194C /* POPCustomAnimation.h in Headers */, + EC6885CA18C7BD6500C6194C /* FloatConversion.h in Headers */, + EC8F016918FFBEB500DF8905 /* POPSpringAnimation.h in Headers */, + EC67007318D3D89F00F7387F /* POPCGUtils.h in Headers */, + EC6885B318C7BD1500C6194C /* POPAnimation.h in Headers */, + EC8F016318FFBE9D00DF8905 /* POPDecayAnimationInternal.h in Headers */, + EC6885CF18C7BD7B00C6194C /* POPDefines.h in Headers */, + EC6885D018C7BD7F00C6194C /* POPAction.h in Headers */, + EC70AC4718CCF4FC0067018C /* POPVector.h in Headers */, + EC8F014718FFBC2D00DF8905 /* POPPropertyAnimationInternal.h in Headers */, + EC8F015D18FFBE8C00DF8905 /* POPDecayAnimation.h in Headers */, + EC6885BA18C7BD3400C6194C /* POPAnimationInternal.h in Headers */, + EC6885B518C7BD1C00C6194C /* POPAnimationEvent.h in Headers */, + EC6885B818C7BD2B00C6194C /* POPAnimationExtras.h in Headers */, + ECA94D0E18ECAE82002E4CEB /* POP.h in Headers */, + EC8F016F18FFBEC200DF8905 /* POPSpringAnimationInternal.h in Headers */, + EC6885C718C7BD5C00C6194C /* POPSpringSolver.h in Headers */, + EC6885C218C7BD4B00C6194C /* POPAnimator.h in Headers */, + EC6885B018C7BD0A00C6194C /* POPAnimatableProperty.h in Headers */, + ECA0D5C118D8196A003720DF /* UnitBezier.h in Headers */, + EC8F015618FFBD5600DF8905 /* POPBasicAnimationInternal.h in Headers */, + EC35DB2A18EE3E820023E077 /* POPAnimationTracer.h in Headers */, + EC6885BD18C7BD3E00C6194C /* POPAnimationRuntime.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + EC191217162FB53A00E0CC76 /* pop */ = { + isa = PBXNativeTarget; + buildConfigurationList = EC19123D162FB53A00E0CC76 /* Build configuration list for PBXNativeTarget "pop" */; + buildPhases = ( + EC191214162FB53A00E0CC76 /* Sources */, + EC191215162FB53A00E0CC76 /* Frameworks */, + EC191216162FB53A00E0CC76 /* CopyFiles */, + EC191294162FB5DD00E0CC76 /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = pop; + productName = POP; + productReference = EC191218162FB53A00E0CC76 /* libpop.a */; + productType = "com.apple.product-type.library.static"; + }; + EC68857E18C7B60000C6194C /* pop-osx */ = { + isa = PBXNativeTarget; + buildConfigurationList = EC6885A318C7B60100C6194C /* Build configuration list for PBXNativeTarget "pop-osx" */; + buildPhases = ( + EC68857A18C7B60000C6194C /* Sources */, + EC68857B18C7B60000C6194C /* Frameworks */, + EC68857C18C7B60000C6194C /* Headers */, + EC68857D18C7B60000C6194C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "pop-osx"; + productName = POP; + productReference = EC68857F18C7B60000C6194C /* pop.framework */; + productType = "com.apple.product-type.framework"; + }; + EC7E319818C93D6500B38170 /* pop-tests-osx */ = { + isa = PBXNativeTarget; + buildConfigurationList = EC7E31A618C93D6600B38170 /* Build configuration list for PBXNativeTarget "pop-tests-osx" */; + buildPhases = ( + 2F7E04AE9A5B41869AF78DA9 /* Check Pods Manifest.lock */, + EC7E319518C93D6500B38170 /* Sources */, + EC7E319618C93D6500B38170 /* Frameworks */, + EC7E319718C93D6500B38170 /* Resources */, + 063BFBF07EAD4D74B19E2BC0 /* Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + EC7E31A518C93D6600B38170 /* PBXTargetDependency */, + ); + name = "pop-tests-osx"; + productName = "pop-tests-osx"; + productReference = EC7E319918C93D6500B38170 /* pop-tests-osx.octest */; + productType = "com.apple.product-type.bundle"; + }; + ECF01ED218C92B7F009E0AD1 /* pop-tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = ECF01EE218C92B80009E0AD1 /* Build configuration list for PBXNativeTarget "pop-tests" */; + buildPhases = ( + 093AEF8FEE094AECB5EDCED2 /* Check Pods Manifest.lock */, + ECF01ECF18C92B7F009E0AD1 /* Sources */, + ECF01ED018C92B7F009E0AD1 /* Frameworks */, + ECF01ED118C92B7F009E0AD1 /* Resources */, + 394D14BF947A476EB827103B /* Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ECF01EE118C92B80009E0AD1 /* PBXTargetDependency */, + ); + name = "pop-tests"; + productName = "pop-tests"; + productReference = ECF01ED318C92B7F009E0AD1 /* pop-tests.octest */; + productType = "com.apple.product-type.bundle"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + EC19120F162FB53A00E0CC76 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0500; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + EC7E319818C93D6500B38170 = { + TestTargetID = EC68857E18C7B60000C6194C; + }; + ECF01ED218C92B7F009E0AD1 = { + TestTargetID = EC191217162FB53A00E0CC76; + }; + }; + }; + buildConfigurationList = EC191212162FB53A00E0CC76 /* Build configuration list for PBXProject "pop" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = EC19120D162FB53A00E0CC76; + productRefGroup = EC191219162FB53A00E0CC76 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + EC191217162FB53A00E0CC76 /* pop */, + EC68857E18C7B60000C6194C /* pop-osx */, + ECF01ED218C92B7F009E0AD1 /* pop-tests */, + EC7E319818C93D6500B38170 /* pop-tests-osx */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + EC68857D18C7B60000C6194C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EC68858B18C7B60000C6194C /* InfoPlist.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EC7E319718C93D6500B38170 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EC7E31A018C93D6500B38170 /* InfoPlist.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + ECF01ED118C92B7F009E0AD1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 063BFBF07EAD4D74B19E2BC0 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Pods-pop-tests-osx-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 093AEF8FEE094AECB5EDCED2 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + 2F7E04AE9A5B41869AF78DA9 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + 394D14BF947A476EB827103B /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Pods-pop-tests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + EC191214162FB53A00E0CC76 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EC191291162FB5B700E0CC76 /* POPAnimation.mm in Sources */, + EC6465D11794B4660014176F /* POPMath.mm in Sources */, + EC8F014B18FFBC8200DF8905 /* POPPropertyAnimation.mm in Sources */, + EC8F016A18FFBEB500DF8905 /* POPSpringAnimation.mm in Sources */, + EC191292162FB5B700E0CC76 /* POPAnimator.mm in Sources */, + EC94B07917D95447003CE2C8 /* TransformationMatrix.cpp in Sources */, + EC70AC4418CCF4FC0067018C /* POPVector.mm in Sources */, + EC191293162FB5B700E0CC76 /* POPAnimatableProperty.mm in Sources */, + EC0AE13216BC73CE001DA2CE /* POPAnimationExtras.mm in Sources */, + EC9553911743E278001E6AF2 /* POPAnimationRuntime.mm in Sources */, + EC94B07E17D95CAA003CE2C8 /* POPLayerExtras.mm in Sources */, + EC35DB2B18EE3E820023E077 /* POPAnimationTracer.mm in Sources */, + 5E17BB2117457345009842B6 /* POPCustomAnimation.mm in Sources */, + EC67007418D3D89F00F7387F /* POPCGUtils.mm in Sources */, + ECD80F1318CFD2EF00AE4303 /* POPGeometry.mm in Sources */, + EC8F015E18FFBE8C00DF8905 /* POPDecayAnimation.mm in Sources */, + EC9997561756A0C300A73F49 /* POPAnimationEvent.mm in Sources */, + EC8F015118FFBD3E00DF8905 /* POPBasicAnimation.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EC68857A18C7B60000C6194C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EC6885C318C7BD4E00C6194C /* POPAnimator.mm in Sources */, + EC6885B618C7BD2200C6194C /* POPAnimationEvent.mm in Sources */, + EC8F014C18FFBC8200DF8905 /* POPPropertyAnimation.mm in Sources */, + EC8F016B18FFBEB500DF8905 /* POPSpringAnimation.mm in Sources */, + EC6885BE18C7BD4000C6194C /* POPAnimationRuntime.mm in Sources */, + EC6885B418C7BD1A00C6194C /* POPAnimation.mm in Sources */, + EC70AC4518CCF4FC0067018C /* POPVector.mm in Sources */, + EC6885BC18C7BD3A00C6194C /* POPMath.mm in Sources */, + EC6885D218C7BD8900C6194C /* TransformationMatrix.cpp in Sources */, + EC6885B118C7BD1000C6194C /* POPAnimatableProperty.mm in Sources */, + EC6885C618C7BD5900C6194C /* POPCustomAnimation.mm in Sources */, + EC35DB2C18EE3E820023E077 /* POPAnimationTracer.mm in Sources */, + EC67007518D3D89F00F7387F /* POPCGUtils.mm in Sources */, + ECD80F1418CFD2EF00AE4303 /* POPGeometry.mm in Sources */, + EC6885C918C7BD6300C6194C /* POPLayerExtras.mm in Sources */, + EC8F015F18FFBE8C00DF8905 /* POPDecayAnimation.mm in Sources */, + EC6885B918C7BD3000C6194C /* POPAnimationExtras.mm in Sources */, + EC8F015218FFBD3E00DF8905 /* POPBasicAnimation.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EC7E319518C93D6500B38170 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EC72875618E13348006EEE54 /* POPCustomAnimationTests.mm in Sources */, + EC7E31AB18C9419000B38170 /* POPAnimatable.mm in Sources */, + EC7E31AD18C9419600B38170 /* POPAnimationTests.mm in Sources */, + EC7E31B318C941A700B38170 /* POPEaseInEaseOutAnimationTests.mm in Sources */, + EC7E31AE18C9419900B38170 /* POPAnimationMRRTests.mm in Sources */, + EC7E31AC18C9419200B38170 /* POPBaseAnimationTests.mm in Sources */, + EC67007718D3D96600F7387F /* POPCGUtils.mm in Sources */, + EC7E31B118C941A200B38170 /* POPDecayAnimationTests.mm in Sources */, + EC7E31B018C9419F00B38170 /* POPAnimatablePropertyTests.mm in Sources */, + EC7E31AF18C9419C00B38170 /* POPAnimationTestsExtras.mm in Sources */, + EC7E31B218C941A400B38170 /* POPSpringAnimationTests.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + ECF01ECF18C92B7F009E0AD1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EC72875518E13348006EEE54 /* POPCustomAnimationTests.mm in Sources */, + ECDA0CC618C92BC900D14897 /* POPAnimatable.mm in Sources */, + ECDA0CC818C92BD200D14897 /* POPAnimationTests.mm in Sources */, + ECDA0CCE18C92BD200D14897 /* POPEaseInEaseOutAnimationTests.mm in Sources */, + ECDA0CC918C92BD200D14897 /* POPAnimationMRRTests.mm in Sources */, + ECDA0CC718C92BD200D14897 /* POPBaseAnimationTests.mm in Sources */, + EC67007618D3D96500F7387F /* POPCGUtils.mm in Sources */, + ECDA0CCC18C92BD200D14897 /* POPDecayAnimationTests.mm in Sources */, + ECDA0CCB18C92BD200D14897 /* POPAnimatablePropertyTests.mm in Sources */, + ECDA0CCA18C92BD200D14897 /* POPAnimationTestsExtras.mm in Sources */, + ECDA0CCD18C92BD200D14897 /* POPSpringAnimationTests.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + EC7E31A518C93D6600B38170 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = EC68857E18C7B60000C6194C /* pop-osx */; + targetProxy = EC7E31A418C93D6600B38170 /* PBXContainerItemProxy */; + }; + ECF01EE118C92B80009E0AD1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = EC191217162FB53A00E0CC76 /* pop */; + targetProxy = ECF01EE018C92B80009E0AD1 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + EC68858918C7B60000C6194C /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + EC68858A18C7B60000C6194C /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + EC7E319E18C93D6500B38170 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + EC7E319F18C93D6500B38170 /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + EC882A7818C91983007829CC /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + EC882A7918C91983007829CC /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 9062CE2A16BC56CC00655F1D /* GCOV */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ECC1DB1318CA291B008C7DEA /* Project-GCOV.xcconfig */; + buildSettings = { + }; + name = GCOV; + }; + 9062CE2B16BC56CC00655F1D /* GCOV */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ECC1DB1A18CA291B008C7DEA /* StaticLibrary-iOS.xcconfig */; + buildSettings = { + GCC_PREFIX_HEADER = "pop/pop-Prefix.pch"; + INFOPLIST_FILE = "POP/pop-Info.plist"; + PRODUCT_NAME = pop; + PUBLIC_HEADERS_FOLDER_PATH = ../Headers/POP; + }; + name = GCOV; + }; + A2BDDD3D185A687F00838797 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ECC1DB1618CA291B008C7DEA /* Project-Profile.xcconfig */; + buildSettings = { + }; + name = Profile; + }; + A2BDDD3E185A687F00838797 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ECC1DB1A18CA291B008C7DEA /* StaticLibrary-iOS.xcconfig */; + buildSettings = { + GCC_PREFIX_HEADER = "pop/pop-Prefix.pch"; + INFOPLIST_FILE = "POP/pop-Info.plist"; + PRODUCT_NAME = pop; + PUBLIC_HEADERS_FOLDER_PATH = ../Headers/POP; + }; + name = Profile; + }; + EC19123B162FB53A00E0CC76 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ECC1DB1218CA291B008C7DEA /* Project-Debug.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + EC19123C162FB53A00E0CC76 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ECC1DB1718CA291B008C7DEA /* Project-Release.xcconfig */; + buildSettings = { + }; + name = Release; + }; + EC19123E162FB53A00E0CC76 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ECC1DB1A18CA291B008C7DEA /* StaticLibrary-iOS.xcconfig */; + buildSettings = { + GCC_PREFIX_HEADER = "pop/pop-Prefix.pch"; + INFOPLIST_FILE = "POP/pop-Info.plist"; + PRODUCT_NAME = pop; + PUBLIC_HEADERS_FOLDER_PATH = ../Headers/POP; + }; + name = Debug; + }; + EC19123F162FB53A00E0CC76 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ECC1DB1A18CA291B008C7DEA /* StaticLibrary-iOS.xcconfig */; + buildSettings = { + GCC_PREFIX_HEADER = "pop/pop-Prefix.pch"; + INFOPLIST_FILE = "POP/pop-Info.plist"; + PRODUCT_NAME = pop; + PUBLIC_HEADERS_FOLDER_PATH = ../Headers/POP; + }; + name = Release; + }; + EC6885A418C7B60100C6194C /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ECC1DB1B18CA291B008C7DEA /* StaticLibrary-OSX.xcconfig */; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_PREFIX_HEADER = "$(PRODUCT_NAME)/$(PRODUCT_NAME)-Prefix.pch"; + INFOPLIST_FILE = "POP/pop-Info.plist"; + PRODUCT_NAME = pop; + PUBLIC_HEADERS_FOLDER_PATH = ../Headers/POP; + }; + name = Debug; + }; + EC6885A518C7B60100C6194C /* GCOV */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ECC1DB1B18CA291B008C7DEA /* StaticLibrary-OSX.xcconfig */; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_PREFIX_HEADER = "$(PRODUCT_NAME)/$(PRODUCT_NAME)-Prefix.pch"; + INFOPLIST_FILE = "POP/pop-Info.plist"; + PRODUCT_NAME = pop; + PUBLIC_HEADERS_FOLDER_PATH = ../Headers/POP; + }; + name = GCOV; + }; + EC6885A618C7B60100C6194C /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ECC1DB1B18CA291B008C7DEA /* StaticLibrary-OSX.xcconfig */; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_PREFIX_HEADER = "$(PRODUCT_NAME)/$(PRODUCT_NAME)-Prefix.pch"; + INFOPLIST_FILE = "POP/pop-Info.plist"; + PRODUCT_NAME = pop; + PUBLIC_HEADERS_FOLDER_PATH = ../Headers/POP; + }; + name = Release; + }; + EC6885A718C7B60100C6194C /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ECC1DB1B18CA291B008C7DEA /* StaticLibrary-OSX.xcconfig */; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_PREFIX_HEADER = "$(PRODUCT_NAME)/$(PRODUCT_NAME)-Prefix.pch"; + INFOPLIST_FILE = "POP/pop-Info.plist"; + PRODUCT_NAME = pop; + PUBLIC_HEADERS_FOLDER_PATH = ../Headers/POP; + }; + name = Profile; + }; + EC7E31A718C93D6600B38170 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D49ED131870840178CA788F1 /* Pods-pop-tests-osx.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "pop-tests-osx/pop-tests-osx-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_SHADOW = NO; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = "pop-tests-osx/pop-tests-osx-Info.plist"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "pop-tests-osx"; + SDKROOT = macosx; + WRAPPER_EXTENSION = octest; + }; + name = Debug; + }; + EC7E31A818C93D6600B38170 /* GCOV */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D49ED131870840178CA788F1 /* Pods-pop-tests-osx.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "pop-tests-osx/pop-tests-osx-Prefix.pch"; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_SHADOW = NO; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = "pop-tests-osx/pop-tests-osx-Info.plist"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + PRODUCT_NAME = "pop-tests-osx"; + SDKROOT = macosx; + WRAPPER_EXTENSION = octest; + }; + name = GCOV; + }; + EC7E31A918C93D6600B38170 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D49ED131870840178CA788F1 /* Pods-pop-tests-osx.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "pop-tests-osx/pop-tests-osx-Prefix.pch"; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_SHADOW = NO; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = "pop-tests-osx/pop-tests-osx-Info.plist"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + PRODUCT_NAME = "pop-tests-osx"; + SDKROOT = macosx; + WRAPPER_EXTENSION = octest; + }; + name = Release; + }; + EC7E31AA18C93D6600B38170 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D49ED131870840178CA788F1 /* Pods-pop-tests-osx.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "pop-tests-osx/pop-tests-osx-Prefix.pch"; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_SHADOW = NO; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = "pop-tests-osx/pop-tests-osx-Info.plist"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + PRODUCT_NAME = "pop-tests-osx"; + SDKROOT = macosx; + WRAPPER_EXTENSION = octest; + }; + name = Profile; + }; + ECF01EE318C92B80009E0AD1 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CD38BFD00B29456D9B40D640 /* Pods-pop-tests.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + "$(DEVELOPER_FRAMEWORKS_DIR)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "pop-tests/pop-tests-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_SHADOW = NO; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "pop-tests"; + WRAPPER_EXTENSION = octest; + }; + name = Debug; + }; + ECF01EE418C92B80009E0AD1 /* GCOV */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CD38BFD00B29456D9B40D640 /* Pods-pop-tests.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_NS_ASSERTIONS = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + "$(DEVELOPER_FRAMEWORKS_DIR)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "pop-tests/pop-tests-Prefix.pch"; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_SHADOW = NO; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + PRODUCT_NAME = "pop-tests"; + WRAPPER_EXTENSION = octest; + }; + name = GCOV; + }; + ECF01EE518C92B80009E0AD1 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CD38BFD00B29456D9B40D640 /* Pods-pop-tests.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_NS_ASSERTIONS = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + "$(DEVELOPER_FRAMEWORKS_DIR)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "pop-tests/pop-tests-Prefix.pch"; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_SHADOW = NO; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + PRODUCT_NAME = "pop-tests"; + WRAPPER_EXTENSION = octest; + }; + name = Release; + }; + ECF01EE618C92B80009E0AD1 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CD38BFD00B29456D9B40D640 /* Pods-pop-tests.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_NS_ASSERTIONS = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + "$(DEVELOPER_FRAMEWORKS_DIR)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "pop-tests/pop-tests-Prefix.pch"; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_SHADOW = NO; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + PRODUCT_NAME = "pop-tests"; + WRAPPER_EXTENSION = octest; + }; + name = Profile; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + EC191212162FB53A00E0CC76 /* Build configuration list for PBXProject "pop" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EC19123B162FB53A00E0CC76 /* Debug */, + 9062CE2A16BC56CC00655F1D /* GCOV */, + EC19123C162FB53A00E0CC76 /* Release */, + A2BDDD3D185A687F00838797 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + EC19123D162FB53A00E0CC76 /* Build configuration list for PBXNativeTarget "pop" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EC19123E162FB53A00E0CC76 /* Debug */, + 9062CE2B16BC56CC00655F1D /* GCOV */, + EC19123F162FB53A00E0CC76 /* Release */, + A2BDDD3E185A687F00838797 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + EC6885A318C7B60100C6194C /* Build configuration list for PBXNativeTarget "pop-osx" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EC6885A418C7B60100C6194C /* Debug */, + EC6885A518C7B60100C6194C /* GCOV */, + EC6885A618C7B60100C6194C /* Release */, + EC6885A718C7B60100C6194C /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + EC7E31A618C93D6600B38170 /* Build configuration list for PBXNativeTarget "pop-tests-osx" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EC7E31A718C93D6600B38170 /* Debug */, + EC7E31A818C93D6600B38170 /* GCOV */, + EC7E31A918C93D6600B38170 /* Release */, + EC7E31AA18C93D6600B38170 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + ECF01EE218C92B80009E0AD1 /* Build configuration list for PBXNativeTarget "pop-tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + ECF01EE318C92B80009E0AD1 /* Debug */, + ECF01EE418C92B80009E0AD1 /* GCOV */, + ECF01EE518C92B80009E0AD1 /* Release */, + ECF01EE618C92B80009E0AD1 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = EC19120F162FB53A00E0CC76 /* Project object */; +} diff --git a/pop.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/pop.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..c986b20f --- /dev/null +++ b/pop.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/pop.xcodeproj/xcshareddata/xcschemes/pop-osx.xcscheme b/pop.xcodeproj/xcshareddata/xcschemes/pop-osx.xcscheme new file mode 100644 index 00000000..9dc3e7f2 --- /dev/null +++ b/pop.xcodeproj/xcshareddata/xcschemes/pop-osx.xcscheme @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pop.xcodeproj/xcshareddata/xcschemes/pop.xcscheme b/pop.xcodeproj/xcshareddata/xcschemes/pop.xcscheme new file mode 100644 index 00000000..289e3fbf --- /dev/null +++ b/pop.xcodeproj/xcshareddata/xcschemes/pop.xcscheme @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pop.xcworkspace/contents.xcworkspacedata b/pop.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..a8fad914 --- /dev/null +++ b/pop.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/pop/POP.h b/pop/POP.h new file mode 100644 index 00000000..4746ff21 --- /dev/null +++ b/pop/POP.h @@ -0,0 +1,29 @@ +/** + 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. + */ + +#ifndef POP_POP_H +#define POP_POP_H + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + + +#endif /* POP_POP_H */ diff --git a/pop/POPAction.h b/pop/POPAction.h new file mode 100644 index 00000000..0827a354 --- /dev/null +++ b/pop/POPAction.h @@ -0,0 +1,66 @@ +/** + 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. + */ + +#ifndef POPACTION_H +#define POPACTION_H + +#import +#import + +#ifdef __cplusplus + +namespace POP { + + /** + @abstract Disables Core Animation actions using RAII. + @discussion The disablement of actions is scoped to the current transaction. + */ + class ActionDisabler + { + BOOL state; + + public: + ActionDisabler() POP_NOTHROW + { + state = [CATransaction disableActions]; + [CATransaction setDisableActions:YES]; + } + + ~ActionDisabler() + { + [CATransaction setDisableActions:state]; + } + }; + + /** + @abstract Enables Core Animation actions using RAII. + @discussion The enablement of actions is scoped to the current transaction. + */ + class ActionEnabler + { + BOOL state; + + public: + ActionEnabler() POP_NOTHROW + { + state = [CATransaction disableActions]; + [CATransaction setDisableActions:NO]; + } + + ~ActionEnabler() + { + [CATransaction setDisableActions:state]; + } + }; + +} + +#endif /* __cplusplus */ + +#endif /* POPACTION_H */ diff --git a/pop/POPAnimatableProperty.h b/pop/POPAnimatableProperty.h new file mode 100644 index 00000000..25371fa5 --- /dev/null +++ b/pop/POPAnimatableProperty.h @@ -0,0 +1,144 @@ +/** + 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 +#import + +@class POPMutableAnimatableProperty; + +/** + @abstract Describes an animatable property. + */ +@interface POPAnimatableProperty : NSObject + +/** + @abstract Property accessor. + @param name The name of the property. + @return The animatable property with that name or nil if it does not exist. + @discussion Common animatable properties are included by default. Use the provided constants to reference. + */ ++ (id)propertyWithName:(NSString *)name; + +/** + @abstract The designated initializer. + @param name The name of the property. + @param block The block used to configure the property on creation. + @return The animatable property with name if it exists, otherwise a newly created instance configured by block. + @discussion Custom properties should use reverse-DNS naming. A newly created instance is only mutable in the scope of block. Once constructed, a property becomes immutable. + */ ++ (id)propertyWithName:(NSString *)name initializer:(void (^)(POPMutableAnimatableProperty *prop))block; + +/** + @abstract The name of the property. + @discussion Used to uniquely identify an animatable property. + */ +@property (readonly, nonatomic, copy) NSString *name; + +/** + @abstract Block used to read values from a property into an array of floats. + */ +@property (readonly, nonatomic, copy) void (^readBlock)(id obj, CGFloat values[]); + +/** + @abstract Block used to write values from an array of floats into a property. + */ +@property (readonly, nonatomic, copy) void (^writeBlock)(id obj, const CGFloat values[]); + +/** + @abstract The threshold value used when determining completion of dynamics simulations. + */ +@property (readonly, nonatomic, assign) CGFloat threshold; + +@end + +/** + @abstract A mutable animatable property intended for configuration. + */ +@interface POPMutableAnimatableProperty : POPAnimatableProperty + +/** + @abstract A read-write version of POPAnimatableProperty name property. + */ +@property (readwrite, nonatomic, copy) NSString *name; + +/** + @abstract A read-write version of POPAnimatableProperty readBlock property. + */ +@property (readwrite, nonatomic, copy) void (^readBlock)(id obj, CGFloat values[]); + +/** + @abstract A read-write version of POPAnimatableProperty writeBlock property. + */ +@property (readwrite, nonatomic, copy) void (^writeBlock)(id obj, const CGFloat values[]); + +/** + @abstract A read-write version of POPAnimatableProperty threshold property. + */ +@property (readwrite, nonatomic, assign) CGFloat threshold; + +@end + +/** + Common CALayer property names. + */ +extern NSString * const kPOPLayerBackgroundColor; +extern NSString * const kPOPLayerBounds; +extern NSString * const kPOPLayerOpacity; +extern NSString * const kPOPLayerPosition; +extern NSString * const kPOPLayerPositionX; +extern NSString * const kPOPLayerPositionY; +extern NSString * const kPOPLayerRotation; +extern NSString * const kPOPLayerRotationX; +extern NSString * const kPOPLayerRotationY; +extern NSString * const kPOPLayerScaleX; +extern NSString * const kPOPLayerScaleXY; +extern NSString * const kPOPLayerScaleY; +extern NSString * const kPOPLayerSize; +extern NSString * const kPOPLayerSubscaleXY; +extern NSString * const kPOPLayerSubtranslationX; +extern NSString * const kPOPLayerSubtranslationXY; +extern NSString * const kPOPLayerSubtranslationY; +extern NSString * const kPOPLayerSubtranslationZ; +extern NSString * const kPOPLayerTranslationX; +extern NSString * const kPOPLayerTranslationXY; +extern NSString * const kPOPLayerTranslationY; +extern NSString * const kPOPLayerTranslationZ; +extern NSString * const kPOPLayerZPosition; + + +/** + Common NSLayoutConstraint property names. + */ +extern NSString * const kPOPLayoutConstraintConstant; + + +#if TARGET_OS_IPHONE + +/** + Common UIView property names. + */ +extern NSString * const kPOPViewAlpha; +extern NSString * const kPOPViewBackgroundColor; +extern NSString * const kPOPViewBounds; +extern NSString * const kPOPViewCenter; +extern NSString * const kPOPViewFrame; +extern NSString * const kPOPViewScaleX; +extern NSString * const kPOPViewScaleXY; +extern NSString * const kPOPViewScaleY; +extern NSString * const kPOPViewSize; + + +/** + Common UITableView property names. + */ +extern NSString * const kPOPTableViewContentOffset; +extern NSString * const kPOPTableViewContentSize; + + +#endif diff --git a/pop/POPAnimatableProperty.mm b/pop/POPAnimatableProperty.mm new file mode 100644 index 00000000..af55592f --- /dev/null +++ b/pop/POPAnimatableProperty.mm @@ -0,0 +1,622 @@ +/** + 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 "POPAnimatableProperty.h" +#import "POPCGUtils.h" +#import "POPAnimationRuntime.h" + +#import + +#import + +#if TARGET_OS_IPHONE +#import +#import +#endif + +#pragma mark - Static + +NSString * const kPOPLayerBackgroundColor = @"backgroundColor"; +NSString * const kPOPLayerBounds = @"bounds"; +NSString * const kPOPLayerOpacity = @"opacity"; +NSString * const kPOPLayerPosition = @"position"; +NSString * const kPOPLayerPositionX = @"positionX"; +NSString * const kPOPLayerPositionY = @"positionY"; +NSString * const kPOPLayerRotation = @"rotation"; +NSString * const kPOPLayerRotationX = @"rotationX"; +NSString * const kPOPLayerRotationY = @"rotationY"; +NSString * const kPOPLayerScaleX = @"scaleX"; +NSString * const kPOPLayerScaleXY = @"scaleXY"; +NSString * const kPOPLayerScaleY = @"scaleY"; +NSString * const kPOPLayerSize = @"size"; +NSString * const kPOPLayerSubscaleXY = @"subscaleXY"; +NSString * const kPOPLayerSubtranslationX = @"subtranslationX"; +NSString * const kPOPLayerSubtranslationXY = @"subtranslationXY"; +NSString * const kPOPLayerSubtranslationY = @"subtranslationY"; +NSString * const kPOPLayerSubtranslationZ = @"subtranslationZ"; +NSString * const kPOPLayerTranslationX = @"translationX"; +NSString * const kPOPLayerTranslationXY = @"translationXY"; +NSString * const kPOPLayerTranslationY = @"translationY"; +NSString * const kPOPLayerTranslationZ = @"translationZ"; +NSString * const kPOPLayerZPosition = @"zPosition"; + +NSString * const kPOPLayoutConstraintConstant = @"layoutConstraint.constant"; + +NSString * const kPOPViewAlpha = @"view.alpha"; +NSString * const kPOPViewBackgroundColor = @"view.backgroundColor"; +NSString * const kPOPViewBounds = kPOPLayerBounds; +NSString * const kPOPViewCenter = @"view.center"; +NSString * const kPOPViewFrame = @"view.frame"; +NSString * const kPOPViewScaleX = @"view.scaleX"; +NSString * const kPOPViewScaleXY = @"view.scaleXY"; +NSString * const kPOPViewScaleY = @"view.scaleY"; +NSString * const kPOPViewSize = kPOPLayerSize; + +NSString * const kPOPTableViewContentOffset = @"tableView.contentOffset"; +NSString * const kPOPTableViewContentSize = @"tableView.contentSize"; + + +/** + State structure internal to static animatable property. + */ +typedef struct +{ + NSString *name; + pop_animatable_read_block readBlock; + pop_animatable_write_block writeBlock; + float threshold; +} _POPStaticAnimatablePropertyState; +typedef _POPStaticAnimatablePropertyState POPStaticAnimatablePropertyState; + +static POPStaticAnimatablePropertyState _staticStates[] = +{ + /* CALayer */ + + {kPOPLayerBackgroundColor, + ^(CALayer *obj, CGFloat values[]) { + POPCGColorGetRGBAComponents(obj.backgroundColor, values); + }, + ^(CALayer *obj, const CGFloat values[]) { + CGColorRef color = POPCGColorRGBACreate(values); + [obj setBackgroundColor:color]; + CGColorRelease(color); + }, + 0.01 + }, + + {kPOPLayerBounds, + ^(CALayer *obj, CGFloat values[]) { + values_from_rect(values, [obj bounds]); + }, + ^(CALayer *obj, const CGFloat values[]) { + [obj setBounds:values_to_rect(values)]; + }, + 1.0 + }, + + {kPOPLayerPosition, + ^(CALayer *obj, CGFloat values[]) { + values_from_point(values, [(CALayer *)obj position]); + }, + ^(CALayer *obj, const CGFloat values[]) { + [obj setPosition:values_to_point(values)]; + }, + 1.0 + }, + + {kPOPLayerPositionX, + ^(CALayer *obj, CGFloat values[]) { + values[0] = [(CALayer *)obj position].x; + }, + ^(CALayer *obj, const CGFloat values[]) { + CGPoint p = [(CALayer *)obj position]; + p.x = values[0]; + [obj setPosition:p]; + }, + 1.0 + }, + + {kPOPLayerPositionY, + ^(CALayer *obj, CGFloat values[]) { + values[0] = [(CALayer *)obj position].y; + }, + ^(CALayer *obj, const CGFloat values[]) { + CGPoint p = [(CALayer *)obj position]; + p.y = values[0]; + [obj setPosition:p]; + }, + 1.0 + }, + + {kPOPLayerOpacity, + ^(CALayer *obj, CGFloat values[]) { + values[0] = [obj opacity]; + }, + ^(CALayer *obj, const CGFloat values[]) { + [obj setOpacity:((float)values[0])]; + }, + 0.01 + }, + + {kPOPLayerScaleX, + ^(CALayer *obj, CGFloat values[]) { + values[0] = POPLayerGetScaleX(obj); + }, + ^(CALayer *obj, const CGFloat values[]) { + POPLayerSetScaleX(obj, values[0]); + }, + 0.005 + }, + + {kPOPLayerScaleY, + ^(CALayer *obj, CGFloat values[]) { + values[0] = POPLayerGetScaleY(obj); + }, + ^(CALayer *obj, const CGFloat values[]) { + POPLayerSetScaleY(obj, values[0]); + }, + 0.005 + }, + + {kPOPLayerScaleXY, + ^(CALayer *obj, CGFloat values[]) { + values_from_point(values, POPLayerGetScaleXY(obj)); + }, + ^(CALayer *obj, const CGFloat values[]) { + POPLayerSetScaleXY(obj, values_to_point(values)); + }, + 0.005 + }, + + {kPOPLayerSubscaleXY, + ^(CALayer *obj, CGFloat values[]) { + values_from_point(values, POPLayerGetSubScaleXY(obj)); + }, + ^(CALayer *obj, const CGFloat values[]) { + POPLayerSetSubScaleXY(obj, values_to_point(values)); + }, + 0.005 + }, + + {kPOPLayerTranslationX, + ^(CALayer *obj, CGFloat values[]) { + values[0] = POPLayerGetTranslationX(obj); + }, + ^(CALayer *obj, const CGFloat values[]) { + POPLayerSetTranslationX(obj, values[0]); + }, + 1.0 + }, + + {kPOPLayerTranslationY, + ^(CALayer *obj, CGFloat values[]) { + values[0] = POPLayerGetTranslationY(obj); + }, + ^(CALayer *obj, const CGFloat values[]) { + POPLayerSetTranslationY(obj, values[0]); + }, + 1.0 + }, + + {kPOPLayerTranslationZ, + ^(CALayer *obj, CGFloat values[]) { + values[0] = POPLayerGetTranslationZ(obj); + }, + ^(CALayer *obj, const CGFloat values[]) { + POPLayerSetTranslationZ(obj, values[0]); + }, + 1.0 + }, + + {kPOPLayerTranslationXY, + ^(CALayer *obj, CGFloat values[]) { + values_from_point(values, POPLayerGetTranslationXY(obj)); + }, + ^(CALayer *obj, const CGFloat values[]) { + POPLayerSetTranslationXY(obj, values_to_point(values)); + }, + 1.0 + }, + + {kPOPLayerSubtranslationX, + ^(CALayer *obj, CGFloat values[]) { + values[0] = POPLayerGetSubTranslationX(obj); + }, + ^(CALayer *obj, const CGFloat values[]) { + POPLayerSetSubTranslationX(obj, values[0]); + }, + 1.0 + }, + + {kPOPLayerSubtranslationY, + ^(CALayer *obj, CGFloat values[]) { + values[0] = POPLayerGetSubTranslationY(obj); + }, + ^(CALayer *obj, const CGFloat values[]) { + POPLayerSetSubTranslationY(obj, values[0]); + }, + 1.0 + }, + + {kPOPLayerSubtranslationZ, + ^(CALayer *obj, CGFloat values[]) { + values[0] = POPLayerGetSubTranslationZ(obj); + }, + ^(CALayer *obj, const CGFloat values[]) { + POPLayerSetSubTranslationZ(obj, values[0]); + }, + 1.0 + }, + + {kPOPLayerSubtranslationXY, + ^(CALayer *obj, CGFloat values[]) { + values_from_point(values, POPLayerGetSubTranslationXY(obj)); + }, + ^(CALayer *obj, const CGFloat values[]) { + POPLayerSetSubTranslationXY(obj, values_to_point(values)); + }, + 1.0 + }, + + {kPOPLayerZPosition, + ^(CALayer *obj, CGFloat values[]) { + values[0] = [obj zPosition]; + }, + ^(CALayer *obj, const CGFloat values[]) { + [obj setZPosition:values[0]]; + }, + 1.0 + }, + + {kPOPLayerSize, + ^(CALayer *obj, CGFloat values[]) { + values_from_size(values, [obj bounds].size); + }, + ^(CALayer *obj, const CGFloat values[]) { + CGSize size = values_to_size(values); + if (size.width < 0. || size.height < 0.) + return; + + CGRect b = [obj bounds]; + b.size = size; + [obj setBounds:b]; + }, + 1.0 + }, + + {kPOPLayerRotation, + ^(CALayer *obj, CGFloat values[]) { + values[0] = POPLayerGetRotation(obj); + }, + ^(CALayer *obj, const CGFloat values[]) { + POPLayerSetRotation(obj, values[0]); + }, + 0.01 + }, + + {kPOPLayerRotationY, + ^(CALayer *obj, CGFloat values[]) { + values[0] = POPLayerGetRotationY(obj); + }, + ^(id obj, const CGFloat values[]) { + POPLayerSetRotationY(obj, values[0]); + }, + 0.01 + }, + + {kPOPLayerRotationX, + ^(CALayer *obj, CGFloat values[]) { + values[0] = POPLayerGetRotationX(obj); + }, + ^(CALayer *obj, const CGFloat values[]) { + POPLayerSetRotationX(obj, values[0]); + }, + 0.01 + }, + + {kPOPLayoutConstraintConstant, + ^(NSLayoutConstraint *obj, CGFloat values[]) { + values[0] = obj.constant; + }, + ^(NSLayoutConstraint *obj, const CGFloat values[]) { + obj.constant = values[0]; + }, + 0.01 + }, + +#if TARGET_OS_IPHONE + /* UIView */ + + {kPOPViewAlpha, + ^(UIView *obj, CGFloat values[]) { + values[0] = obj.alpha; + }, + ^(UIView *obj, const CGFloat values[]) { + obj.alpha = values[0]; + }, + 1.0 + }, + + {kPOPViewBackgroundColor, + ^(UIView *obj, CGFloat values[]) { + POPUIColorGetRGBAComponents(obj.backgroundColor, values); + }, + ^(UIView *obj, const CGFloat values[]) { + obj.backgroundColor = POPUIColorRGBACreate(values); + }, + 1.0 + }, + + {kPOPViewCenter, + ^(UIView *obj, CGFloat values[]) { + values_from_point(values, obj.center); + }, + ^(UIView *obj, const CGFloat values[]) { + obj.center = values_to_point(values); + }, + 1.0 + }, + + {kPOPViewFrame, + ^(UIView *obj, CGFloat values[]) { + values_from_rect(values, obj.frame); + }, + ^(UIView *obj, const CGFloat values[]) { + obj.frame = values_to_rect(values); + }, + 1.0 + }, + + {kPOPViewScaleX, + ^(UIView *obj, CGFloat values[]) { + values[0] = POPLayerGetScaleX(obj.layer); + }, + ^(UIView *obj, const CGFloat values[]) { + POPLayerSetScaleX(obj.layer, values[0]); + }, + 0.005 + }, + + {kPOPViewScaleY, + ^(UIView *obj, CGFloat values[]) { + values[0] = POPLayerGetScaleY(obj.layer); + }, + ^(UIView *obj, const CGFloat values[]) { + POPLayerSetScaleY(obj.layer, values[0]); + }, + 0.005 + }, + + {kPOPViewScaleXY, + ^(UIView *obj, CGFloat values[]) { + values_from_point(values, POPLayerGetScaleXY(obj.layer)); + }, + ^(UIView *obj, const CGFloat values[]) { + POPLayerSetScaleXY(obj.layer, values_to_point(values)); + }, + 0.005 + }, + + /* UITableView */ + + {kPOPTableViewContentOffset, + ^(UITableView *obj, CGFloat values[]) { + values_from_point(values, obj.contentOffset); + }, + ^(UITableView *obj, const CGFloat values[]) { + obj.contentOffset = values_to_point(values); + }, + 1.0 + }, + + {kPOPTableViewContentSize, + ^(UITableView *obj, CGFloat values[]) { + values_from_size(values, obj.contentSize); + }, + ^(UITableView *obj, const CGFloat values[]) { + obj.contentSize = values_to_size(values); + }, + 1.0 + }, + +#endif + +}; + +static NSUInteger staticIndexWithName(NSString *aName) +{ + NSUInteger idx = 0; + + while (idx < POP_ARRAY_COUNT(_staticStates)) { + if ([_staticStates[idx].name isEqualToString:aName]) + return idx; + idx++; + } + + return NSNotFound; +} + +/** + Concrete static property class. + */ +@interface POPStaticAnimatableProperty : POPAnimatableProperty +{ +@public + POPStaticAnimatablePropertyState *_state; +} +@end + +@implementation POPStaticAnimatableProperty + +- (NSString *)name +{ + return _state->name; +} + +- (pop_animatable_read_block)readBlock +{ + return _state->readBlock; +} + +- (pop_animatable_write_block)writeBlock +{ + return _state->writeBlock; +} + +- (CGFloat)threshold +{ + return _state->threshold; +} + +@end + +#pragma mark - Concrete + +/** + Concrete immutable property class. + */ +@interface POPConcreteAnimatableProperty : POPAnimatableProperty +- (instancetype)initWithName:(NSString *)name readBlock:(pop_animatable_read_block)read writeBlock:(pop_animatable_write_block)write threshold:(CGFloat)threshold; +@end + +@implementation POPConcreteAnimatableProperty + +// default synthesis +@synthesize name, readBlock, writeBlock, threshold; + +- (instancetype)initWithName:(NSString *)aName readBlock:(pop_animatable_read_block)aReadBlock writeBlock:(pop_animatable_write_block)aWriteBlock threshold:(CGFloat)aThreshold +{ + self = [super init]; + if (nil != self) { + name = [aName copy]; + readBlock = [aReadBlock copy]; + writeBlock = [aWriteBlock copy]; + threshold = aThreshold; + } + return self; +} +@end + +#pragma mark - Mutable + +@implementation POPMutableAnimatableProperty + +// default synthesis +@synthesize name, readBlock, writeBlock, threshold; + +@end + +#pragma mark - Cluster + +/** + Singleton placeholder property class to support class cluster. + */ +@interface POPPlaceholderAnimatableProperty : POPAnimatableProperty + +@end + +@implementation POPPlaceholderAnimatableProperty + +// default synthesis +@synthesize name, readBlock, writeBlock, threshold; + +@end + +/** + Cluster class. + */ +@implementation POPAnimatableProperty + +// avoid creating backing ivars +@dynamic name, readBlock, writeBlock, threshold; + +static POPAnimatableProperty *placeholder = nil; + ++ (void)initialize +{ + if (self == [POPAnimatableProperty class]) { + placeholder = [POPPlaceholderAnimatableProperty alloc]; + } +} + ++ (id)allocWithZone:(struct _NSZone *)zone +{ + if (self == [POPAnimatableProperty class]) { + if (nil == placeholder) { + placeholder = [super allocWithZone:zone]; + } + return placeholder; + } + return [super allocWithZone:zone]; +} + +- (id)copyWithZone:(NSZone *)zone +{ + if ([self isKindOfClass:[POPMutableAnimatableProperty class]]) { + POPConcreteAnimatableProperty *copyProperty = [[POPConcreteAnimatableProperty alloc] initWithName:self.name readBlock:self.readBlock writeBlock:self.writeBlock threshold:self.threshold]; + return copyProperty; + } else { + return self; + } +} + +- (id)mutableCopyWithZone:(NSZone *)zone +{ + POPMutableAnimatableProperty *copyProperty = [[POPMutableAnimatableProperty alloc] init]; + copyProperty.name = self.name; + copyProperty.readBlock = self.readBlock; + copyProperty.writeBlock = self.writeBlock; + copyProperty.threshold = self.threshold; + return copyProperty; +} + ++ (id)propertyWithName:(NSString *)aName +{ + return [self propertyWithName:aName initializer:NULL]; +} + ++ (id)propertyWithName:(NSString *)aName initializer:(void (^)(POPMutableAnimatableProperty *prop))aBlock +{ + POPAnimatableProperty *prop = nil; + + static NSMutableDictionary *_propertyDict = nil; + if (nil == _propertyDict) { + _propertyDict = [[NSMutableDictionary alloc] initWithCapacity:10]; + } + + prop = _propertyDict[aName]; + if (nil != prop) { + return prop; + } + + NSUInteger staticIdx = staticIndexWithName(aName); + + if (NSNotFound != staticIdx) { + POPStaticAnimatableProperty *staticProp = [[POPStaticAnimatableProperty alloc] init]; + staticProp->_state = &_staticStates[staticIdx]; + _propertyDict[aName] = staticProp; + prop = staticProp; + } else if (NULL != aBlock) { + POPMutableAnimatableProperty *mutableProp = [[POPMutableAnimatableProperty alloc] init]; + mutableProp.name = aName; + mutableProp.threshold = 1.0; + aBlock(mutableProp); + prop = [mutableProp copy]; + } + + return prop; +} + +- (NSString *)description +{ + NSMutableString *s = [NSMutableString stringWithFormat:@"%@ name:%@ threshold:%f", super.description, self.name, self.threshold]; + return s; +} + +@end diff --git a/pop/POPAnimation.h b/pop/POPAnimation.h new file mode 100644 index 00000000..4b62afc8 --- /dev/null +++ b/pop/POPAnimation.h @@ -0,0 +1,134 @@ +/** + 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 + +#import +#import + +@class CAMediaTimingFunction; + +/** + @abstract The abstract animation base class. + @discussion Instantiate and use one of the concrete animation subclasses. + */ +@interface POPAnimation : NSObject + +/** + @abstract The name of the animation. + @discussion Optional property to help identify the animation. + */ +@property (copy, nonatomic) NSString *name; + +/** + @abstract The beginTime of the animation in media time. + @discussion Defaults to 0 and starts immediately. + */ +@property (assign, nonatomic) CFTimeInterval beginTime; + +/** + @abstract The animation delegate. + @discussion See {@ref POPAnimationDelegate} for details. + */ +@property (weak, nonatomic) id delegate; + +/** + @abstract The animation tracer. + @discussion Returns the existing tracer, creating one if needed. Call start/stop on the tracer to toggle event collection. + */ +@property (readonly, nonatomic) POPAnimationTracer *tracer; + +/** + @abstract Optional block called on animation completion. + */ +@property (copy, nonatomic) void (^completionBlock)(POPAnimation *anim, BOOL finished); + +/** + @abstract Flag indicating whether animation should be removed on completion. + @discussion Setting to NO can facilitate animation reuse. Defaults to YES. + */ +@property (assign, nonatomic) BOOL removedOnCompletion; + +/** + @abstract Flag indicating whether animation is paused. + @discussion A paused animation is excluded from the list of active animations. On initial creation, defaults to YES. On animation addition, the animation is implicity unpaused. On animation completion, the animation is implicity paused including for animations with removedOnCompletion set to NO. + */ +@property (assign, nonatomic, getter = isPaused) BOOL paused; + +@end + +/** + @abstract The animation delegate. + */ +@protocol POPAnimationDelegate +@optional + +/** + @abstract Called on animation start. + @param anim The relevant animation. + */ +- (void)pop_animationDidStart:(POPAnimation *)anim; + +/** + @abstract Called when value meets or exceeds to value. + @param anim The relevant animation. + */ +- (void)pop_animationDidReachToValue:(POPAnimation *)anim; + +/** + @abstract Called on animation stop. + @param anim The relevant animation. + @param finished Flag indicating finished state. Flag is true if the animation reached completion before being removed. + */ +- (void)pop_animationDidStop:(POPAnimation *)anim finished:(BOOL)finished; + +/** + @abstract Called each frame animation is applied. + @param anim The relevant animation. + */ +- (void)pop_animationDidApply:(POPAnimation *)anim; + +@end + + +@interface NSObject (POP) + +/** + @abstract Add an animation to the reciver. + @param anim The animation to add. + @param key The key used to identify the animation. + @discussion The 'key' may be any string such that only one animation per unique key is added per object. + */ +- (void)pop_addAnimation:(POPAnimation *)anim forKey:(NSString *)key; + +/** + @abstract Remove all animations attached to the receiver. + */ +- (void)pop_removeAllAnimations; + +/** + @abstract Remove any animation attached to the receiver for 'key'. + @param key The key used to identify the animation. + */ +- (void)pop_removeAnimationForKey:(NSString *)key; + +/** + @abstract Returns an array containing the keys of all animations currently attached to the receiver. + @param The order of keys reflects the order in which animations will be applied. + */ +- (NSArray *)pop_animationKeys; + +/** + @abstract Returns any animation attached to the receiver. + @param key The key used to identify the animation. + @returns The animation currently attached, or nil if no such animation exists. + */ +- (id)pop_animationForKey:(NSString *)key; + +@end diff --git a/pop/POPAnimation.mm b/pop/POPAnimation.mm new file mode 100644 index 00000000..91b3ca70 --- /dev/null +++ b/pop/POPAnimation.mm @@ -0,0 +1,228 @@ +/** + 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 "POPAnimationInternal.h" +#import "POPAnimationTracerInternal.h" + +#import +#include + +#import "POPAnimationExtras.h" +#import "POPAnimationRuntime.h" +#import "POPAnimatorPrivate.h" +#import "POPAction.h" + +using namespace POP; + +#pragma mark - POPAnimation + +@implementation POPAnimation + +#pragma mark - Lifecycle + +- (id)init +{ + [NSException raise:NSStringFromClass([self class]) format:@"Attempting to instantiate an abstract class. Use a concrete subclass instead."]; + return nil; +} + +- (id)_init +{ + self = [super init]; + if (nil != self) { + [self _initState]; + } + return self; +} + +- (void)_initState +{ + _state = new POPAnimationState(self); +} + +- (void)dealloc +{ + if (_state) { + delete _state; + _state = NULL; + }; +} + +#pragma mark - Properties + +- (id)delegate +{ + return _state->delegate; +} + +- (void)setDelegate:(id)delegate +{ + _state->setDelegate(delegate); +} + +- (BOOL)isPaused +{ + return _state->paused; +} + +- (void)setPaused:(BOOL)paused +{ + _state->setPaused(paused ? true : false); +} + +FB_PROPERTY_GET(POPAnimationState, type, POPAnimationType); +DEFINE_RW_PROPERTY_OBJ_COPY(POPAnimationState, completionBlock, setCompletionBlock:, POPAnimationCompletionBlock); +DEFINE_RW_PROPERTY_OBJ_COPY(POPAnimationState, name, setName:, NSString*); +DEFINE_RW_PROPERTY(POPAnimationState, beginTime, setBeginTime:, CFTimeInterval); +DEFINE_RW_FLAG(POPAnimationState, removedOnCompletion, removedOnCompletion, setRemovedOnCompletion:); + +- (id)valueForUndefinedKey:(NSString *)key +{ + return _state->dict[key]; +} + +- (void)setValue:(id)value forUndefinedKey:(NSString *)key +{ + if (!value) { + [_state->dict removeObjectForKey:key]; + } else { + if (!_state->dict) + _state->dict = [[NSMutableDictionary alloc] init]; + _state->dict[key] = value; + } +} + +- (POPAnimationTracer *)tracer +{ + POPAnimationState *s = POPAnimationGetState(self); + if (!s->tracer) { + s->tracer = [[POPAnimationTracer alloc] initWithAnimation:self]; + } + return s->tracer; +} + +- (NSString *)description +{ + NSMutableString *s = [NSMutableString stringWithFormat:@"<%@:%p", NSStringFromClass([self class]), self]; + [self _appendDescription:s debug:NO]; + [s appendString:@">"]; + return s; +} + +- (NSString *)debugDescription +{ + NSMutableString *s = [NSMutableString stringWithFormat:@"<%@:%p", NSStringFromClass([self class]), self]; + [self _appendDescription:s debug:YES]; + [s appendString:@">"]; + return s; +} + +#pragma mark - Utility + +POPAnimationState *POPAnimationGetState(POPAnimation *a) +{ + return a->_state; +} + +- (BOOL)_advance:(id)object currentTime:(CFTimeInterval)currentTime elapsedTime:(CFTimeInterval)elapsedTime +{ + return YES; +} + +- (void)_appendDescription:(NSMutableString *)s debug:(BOOL)debug +{ + if (_state->name) + [s appendFormat:@"; name = %@", _state->name]; + + if (!self.removedOnCompletion) + [s appendFormat:@"; removedOnCompletion = %@", POPStringFromBOOL(self.removedOnCompletion)]; + + if (debug) { + if (_state->active) + [s appendFormat:@"; active = %@", POPStringFromBOOL(_state->active)]; + + if (_state->paused) + [s appendFormat:@"; paused = %@", POPStringFromBOOL(_state->paused)]; + } + + if (_state->beginTime) { + [s appendFormat:@"; beginTime = %f", _state->beginTime]; + } + + for (NSString *key in _state->dict) { + [s appendFormat:@"; %@ = %@", key, _state->dict[key]]; + } +} + +@end + + +#pragma mark - POPPropertyAnimation + +#pragma mark - POPBasicAnimation + +#pragma mark - POPDecayAnimation + +@implementation NSObject (POP) + +- (void)pop_addAnimation:(POPAnimation *)anim forKey:(NSString *)key +{ + [[POPAnimator sharedAnimator] addAnimation:anim forObject:self key:key]; +} + +- (void)pop_removeAllAnimations +{ + [[POPAnimator sharedAnimator] removeAllAnimationsForObject:self]; +} + +- (void)pop_removeAnimationForKey:(NSString *)key +{ + [[POPAnimator sharedAnimator] removeAnimationForObject:self key:key]; +} + +- (NSArray *)pop_animationKeys +{ + return [[POPAnimator sharedAnimator] animationKeysForObject:self]; +} + +- (id)pop_animationForKey:(NSString *)key +{ + return [[POPAnimator sharedAnimator] animationForObject:self key:key]; +} + +@end + +@implementation NSProxy (POP) + +- (void)pop_addAnimation:(POPAnimation *)anim forKey:(NSString *)key +{ + [[POPAnimator sharedAnimator] addAnimation:anim forObject:self key:key]; +} + +- (void)pop_removeAllAnimations +{ + [[POPAnimator sharedAnimator] removeAllAnimationsForObject:self]; +} + +- (void)pop_removeAnimationForKey:(NSString *)key +{ + [[POPAnimator sharedAnimator] removeAnimationForObject:self key:key]; +} + +- (NSArray *)pop_animationKeys +{ + return [[POPAnimator sharedAnimator] animationKeysForObject:self]; +} + +- (id)pop_animationForKey:(NSString *)key +{ + return [[POPAnimator sharedAnimator] animationForObject:self key:key]; +} + +@end diff --git a/pop/POPAnimationEvent.h b/pop/POPAnimationEvent.h new file mode 100644 index 00000000..cd1414f6 --- /dev/null +++ b/pop/POPAnimationEvent.h @@ -0,0 +1,68 @@ +/** + 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 + +/** + @abstract Enumeraton of animation event types. + */ +typedef NS_ENUM(NSUInteger, POPAnimationEventType) { + kPOPAnimationEventPropertyRead = 0, + kPOPAnimationEventPropertyWrite, + kPOPAnimationEventToValueUpdate, + kPOPAnimationEventFromValueUpdate, + kPOPAnimationEventVelocityUpdate, + kPOPAnimationEventBouncinessUpdate, + kPOPAnimationEventSpeedUpdate, + kPOPAnimationEventFrictionUpdate, + kPOPAnimationEventMassUpdate, + kPOPAnimationEventTensionUpdate, + kPOPAnimationEventDidStart, + kPOPAnimationEventDidStop, + kPOPAnimationEventDidReachToValue, +}; + +/** + @abstract The base animation event class. + */ +@interface POPAnimationEvent : NSObject + +/** + @abstract The event type. See {@ref POPAnimationEventType} for possible values. + */ +@property (readonly, nonatomic, assign) POPAnimationEventType type; + +/** + @abstract The time of event. + */ +@property (readonly, nonatomic, assign) CFTimeInterval time; + +/** + @abstract Optional string describing the animation at time of event. + */ +@property (readonly, nonatomic, copy) NSString *animationDescription; + +@end + +/** + @abstract An animation event subclass for recording value and velocity. + */ +@interface POPAnimationValueEvent : POPAnimationEvent + +/** + @abstract The value recorded. + */ +@property (readonly, nonatomic, strong) id value; + +/** + @abstract The velocity recorded, if any. + */ +@property (readonly, nonatomic, strong) id velocity; + +@end diff --git a/pop/POPAnimationEvent.mm b/pop/POPAnimationEvent.mm new file mode 100644 index 00000000..3456e566 --- /dev/null +++ b/pop/POPAnimationEvent.mm @@ -0,0 +1,101 @@ +/** + 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 "POPAnimationEvent.h" +#import "POPAnimationEventInternal.h" + +static NSString *stringFromType(POPAnimationEventType aType) +{ + switch (aType) { + case kPOPAnimationEventPropertyRead: + return @"read"; + case kPOPAnimationEventPropertyWrite: + return @"write"; + case kPOPAnimationEventToValueUpdate: + return @"toValue"; + case kPOPAnimationEventFromValueUpdate: + return @"fromValue"; + case kPOPAnimationEventVelocityUpdate: + return @"velocity"; + case kPOPAnimationEventSpeedUpdate: + return @"speed"; + case kPOPAnimationEventBouncinessUpdate: + return @"bounciness"; + case kPOPAnimationEventFrictionUpdate: + return @"friction"; + case kPOPAnimationEventMassUpdate: + return @"mass"; + case kPOPAnimationEventTensionUpdate: + return @"tension"; + case kPOPAnimationEventDidStart: + return @"didStart"; + case kPOPAnimationEventDidStop: + return @"didStop"; + case kPOPAnimationEventDidReachToValue: + return @"didReachToValue"; + default: + return nil; + } +} + +@implementation POPAnimationEvent + +- (instancetype)initWithType:(POPAnimationEventType)aType time:(CFTimeInterval)aTime +{ + self = [super init]; + if (nil != self) { + _type = aType; + _time = aTime; + } + return self; +} + +- (NSString *)description +{ + NSMutableString *s = [NSMutableString stringWithFormat:@""]; + return s; +} + +// subclass override +- (void)_appendDescription:(NSMutableString *)s +{ + if (0 != _animationDescription.length) { + [s appendFormat:@"; animation = %@", _animationDescription]; + } +} + +@end + +@implementation POPAnimationValueEvent + +- (instancetype)initWithType:(POPAnimationEventType)aType time:(CFTimeInterval)aTime value:(id)aValue +{ + self = [self initWithType:aType time:aTime]; + if (nil != self) { + _value = aValue; + } + return self; +} + +- (void)_appendDescription:(NSMutableString *)s +{ + [super _appendDescription:s]; + + if (nil != _value) { + [s appendFormat:@"; value = %@", _value]; + } + + if (nil != _velocity) { + [s appendFormat:@"; velocity = %@", _velocity]; + } +} + +@end diff --git a/pop/POPAnimationEventInternal.h b/pop/POPAnimationEventInternal.h new file mode 100644 index 00000000..398d59bd --- /dev/null +++ b/pop/POPAnimationEventInternal.h @@ -0,0 +1,41 @@ +/** + 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 + +#import "POPAnimationEvent.h" + +@interface POPAnimationEvent () + +/** + @abstract Default initializer. + */ +- (instancetype)initWithType:(POPAnimationEventType)type time:(CFTimeInterval)time; + +/** + @abstract Readwrite redefinition of public property. + */ +@property (readwrite, nonatomic, copy) NSString *animationDescription; + +@end + +@interface POPAnimationValueEvent () + +/** + @abstract Default initializer. + */ +- (instancetype)initWithType:(POPAnimationEventType)type time:(CFTimeInterval)time value:(id)value; + +/** + @abstract Readwrite redefinition of public property. + */ +@property (readwrite, nonatomic, strong) id velocity; + +@end + diff --git a/pop/POPAnimationExtras.h b/pop/POPAnimationExtras.h new file mode 100644 index 00000000..6c06800a --- /dev/null +++ b/pop/POPAnimationExtras.h @@ -0,0 +1,43 @@ +/** + 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 + +#import +#import + +/** + @abstract The current drag coefficient. + @discussion A value greater than 1.0 indicates Simulator slow-motion animations are enabled. Defaults to 1.0. + */ +extern CGFloat POPAnimationDragCoefficient(); + +@interface CAAnimation (POPAnimationExtras) + +/** + @abstract Apply the current drag coefficient to animation speed. + @discussion Convenience utility to respect Simulator slow-motion animation settings. + */ +- (void)pop_applyDragCoefficient; + +@end + +@interface POPSpringAnimation (POPAnimationExtras) + +/** + @abstract Converts from spring bounciness and speed to tension, friction and mass dynamics values. + */ ++ (void)convertBounciness:(CGFloat)bounciness speed:(CGFloat)speed toTension:(CGFloat *)outTension friction:(CGFloat *)outFriction mass:(CGFloat *)outMass; + +/** + @abstract Converts from dynamics tension, friction and mass to spring bounciness and speed values. + */ ++ (void)convertTension:(CGFloat)tension friction:(CGFloat)friction toBounciness:(CGFloat *)outBounciness speed:(CGFloat *)outSpeed; + +@end diff --git a/pop/POPAnimationExtras.mm b/pop/POPAnimationExtras.mm new file mode 100644 index 00000000..d7abbfe6 --- /dev/null +++ b/pop/POPAnimationExtras.mm @@ -0,0 +1,117 @@ +/** + 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 "POPAnimationExtras.h" + +#import "POPAnimationPrivate.h" +#import "POPMath.h" + +#if TARGET_OS_IPHONE +#import +#endif + +#if TARGET_IPHONE_SIMULATOR +UIKIT_EXTERN CGFloat UIAnimationDragCoefficient(); // UIKit private drag coeffient, use judiciously +#endif + +CGFloat POPAnimationDragCoefficient() +{ +#if TARGET_IPHONE_SIMULATOR + return UIAnimationDragCoefficient(); +#else + return 1.0; +#endif +} + +@implementation CAAnimation (POPAnimationExtras) + +- (void)pop_applyDragCoefficient +{ + CGFloat k = POPAnimationDragCoefficient(); + if (k != 0 && k != 1) + self.speed = 1 / k; +} + +@end + +@implementation POPSpringAnimation (POPAnimationExtras) + +static const CGFloat POPBouncy3NormalizationRange = 20.0; +static const CGFloat POPBouncy3NormalizationScale = 1.7; +static const CGFloat POPBouncy3BouncinessNormalizedMin = 0.0; +static const CGFloat POPBouncy3BouncinessNormalizedMax = 0.8; +static const CGFloat POPBouncy3SpeedNormalizedMin = 0.5; +static const CGFloat POPBouncy3SpeedNormalizedMax = 200; +static const CGFloat POPBouncy3FrictionInterpolationMax = 0.01; + ++ (void)convertBounciness:(CGFloat)bounciness speed:(CGFloat)speed toTension:(CGFloat *)outTension friction:(CGFloat *)outFriction mass:(CGFloat *)outMass +{ + double b = normalize(bounciness / POPBouncy3NormalizationScale, 0, POPBouncy3NormalizationRange); + b = project_normal(b, POPBouncy3BouncinessNormalizedMin, POPBouncy3BouncinessNormalizedMax); + + double s = normalize(speed / POPBouncy3NormalizationScale, 0, POPBouncy3NormalizationRange); + + CGFloat tension = project_normal(s, POPBouncy3SpeedNormalizedMin, POPBouncy3SpeedNormalizedMax); + CGFloat friction = quadratic_out_interpolation(b, b3_nobounce(tension), POPBouncy3FrictionInterpolationMax); + + tension = POP_ANIMATION_TENSION_FOR_QC_TENSION(tension); + friction = POP_ANIMATION_FRICTION_FOR_QC_FRICTION(friction); + + if (outTension) { + *outTension = tension; + } + + if (outFriction) { + *outFriction = friction; + } + + if (outMass) { + *outMass = 1.0; + } +} + ++ (void)convertTension:(CGFloat)tension friction:(CGFloat)friction toBounciness:(CGFloat *)outBounciness speed:(CGFloat *)outSpeed +{ + // Convert to QC values, in which our calculations are done. + CGFloat qcFriction = QC_FRICTION_FOR_POP_ANIMATION_FRICTION(friction); + CGFloat qcTension = QC_TENSION_FOR_POP_ANIMATION_TENSION(tension); + + // Friction is a function of bounciness and tension, according to the following: + // friction = quadratic_out_interpolation(b, b3_nobounce(tension), POPBouncy3FrictionInterpolationMax); + // Solve for bounciness, given a tension and friction. + + CGFloat nobounceTension = b3_nobounce(qcTension); + CGFloat bounciness1, bounciness2; + + quadratic_solve((nobounceTension - POPBouncy3FrictionInterpolationMax), // a + 2 * (POPBouncy3FrictionInterpolationMax - nobounceTension), // b + (nobounceTension - qcFriction), // c + bounciness1, // x1 + bounciness2); // x2 + + + // Choose the quadratic solution within the normalized bounciness range + CGFloat projectedNormalizedBounciness = (bounciness2 < POPBouncy3BouncinessNormalizedMax) ? bounciness2 : bounciness1; + CGFloat projectedNormalizedSpeed = qcTension; + + // Reverse projection + normalization + CGFloat bounciness = ((POPBouncy3NormalizationRange * POPBouncy3NormalizationScale) / (POPBouncy3BouncinessNormalizedMax - POPBouncy3BouncinessNormalizedMin)) * (projectedNormalizedBounciness - POPBouncy3BouncinessNormalizedMin); + CGFloat speed = ((POPBouncy3NormalizationRange * POPBouncy3NormalizationScale) / (POPBouncy3SpeedNormalizedMax - POPBouncy3SpeedNormalizedMin)) * (projectedNormalizedSpeed - POPBouncy3SpeedNormalizedMin); + + // Write back results + if (outBounciness) { + *outBounciness = bounciness; + } + + if (outSpeed) { + *outSpeed = speed; + } +} + +@end diff --git a/pop/POPAnimationInternal.h b/pop/POPAnimationInternal.h new file mode 100644 index 00000000..777d441e --- /dev/null +++ b/pop/POPAnimationInternal.h @@ -0,0 +1,485 @@ +/** + 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 + +#import "POPAnimation.h" +#import "POPAnimationRuntime.h" +#import "POPAnimationTracer.h" +#import "POPAnimationTracerInternal.h" +#import "POPSpringSolver.h" +#import "POPVector.h" +#import "POPAction.h" +#import "POPMath.h" + +using namespace POP; + +/** + Enumeration of supported animation types. + */ +enum POPAnimationType +{ + kPOPAnimationSpring, + kPOPAnimationDecay, + kPOPAnimationBasic, + kPOPAnimationCustom, +}; + +typedef struct +{ + CGFloat progress; + bool reached; +} POPProgressMarker; + +typedef void (^POPAnimationCompletionBlock)(POPAnimation *anim, BOOL finished); + +@interface POPAnimation() +- (instancetype)_init; + +@property (assign, nonatomic) SpringSolver4d *solver; +@property (readonly, nonatomic) POPAnimationType type; + +/** + The current animation value, updated while animation is progressing. + */ +@property (copy, nonatomic, readonly) id currentValue; + +/** + An array of optional progress markers. For each marker specified, the animation delegate will be informed when progress meets or exceeds the value specified. Specifying values outside of the [0, 1] range will give undefined results. + */ +@property (copy, nonatomic) NSArray *progressMarkers; + +/** + Return YES to indicate animation should continue animating. + */ +- (BOOL)_advance:(id)object currentTime:(CFTimeInterval)currentTime elapsedTime:(CFTimeInterval)elapsedTime; + +/** + Subclass override point to append animation description. + */ +- (void)_appendDescription:(NSMutableString *)s debug:(BOOL)debug; + +@end + +NS_INLINE NSString *describe(VectorConstRef vec) +{ + return NULL == vec ? @"null" : vec->toString(); +} + +NS_INLINE Vector4r vector4(VectorConstRef vec) +{ + return NULL == vec ? Vector4r::Zero() : vec->vector4r(); +} + +NS_INLINE Vector4d vector4d(VectorConstRef vec) +{ + if (NULL == vec) { + return Vector4d::Zero(); + } else { + return vec->vector4r().cast(); + } +} + +NS_INLINE bool vec_equal(VectorConstRef v1, VectorConstRef v2) +{ + if (v1 == v2) { + return true; + } + if (!v1 || !v2) { + return false; + } + return *v1 == *v2; +} + +NS_INLINE CGFloat * vec_data(VectorRef vec) +{ + return NULL == vec ? NULL : vec->data(); +} + +template +struct ComputeProgressFunctor { + CGFloat operator()(const T &value, const T &start, const T &end) const { + return 0; + } +}; + +template<> +struct ComputeProgressFunctor { + CGFloat operator()(const Vector4r &value, const Vector4r &start, const Vector4r &end) const { + CGFloat s = (value - start).squaredNorm(); // distance from start + CGFloat e = (value - end).squaredNorm(); // distance from end + CGFloat d = (end - start).squaredNorm(); // distance from start to end + + if (0 == d) { + return 1; + } else if (s > e) { + // s -------- p ---- e OR s ------- e ---- p + return sqrtr(s/d); + } else { + // s --- p --------- e OR p ---- s ------- e + return 1 - sqrtr(e/d); + } + } +}; + +struct _POPAnimationState; +struct _POPDecayAnimationState; +struct _POPPropertyAnimationState; + +extern _POPAnimationState *POPAnimationGetState(POPAnimation *a); + + +#define FB_FLAG_GET(stype, flag, getter) \ +- (BOOL)getter { \ + return ((stype *)_state)->flag; \ +} + +#define FB_FLAG_SET(stype, flag, mutator) \ +- (void)mutator (BOOL)value { \ + if (value == ((stype *)_state)->flag) \ + return; \ + ((stype *)_state)->flag = value; \ +} + +#define DEFINE_RW_FLAG(stype, flag, getter, mutator) \ + FB_FLAG_GET (stype, flag, getter) \ + FB_FLAG_SET (stype, flag, mutator) + +#define FB_PROPERTY_GET(stype, property, ctype) \ +- (ctype)property { \ + return ((stype *)_state)->property; \ +} + +#define FB_PROPERTY_SET(stype, property, mutator, ctype, ...) \ +- (void)mutator (ctype)value { \ + if (value == ((stype *)_state)->property) \ + return; \ + ((stype *)_state)->property = value; \ + __VA_ARGS__ \ +} + +#define FB_PROPERTY_SET_OBJ_COPY(stype, property, mutator, ctype, ...) \ +- (void)mutator (ctype)value { \ + if (value == ((stype *)_state)->property) \ + return; \ + ((stype *)_state)->property = [value copy]; \ + __VA_ARGS__ \ +} + +#define DEFINE_RW_PROPERTY(stype, flag, mutator, ctype, ...) \ + FB_PROPERTY_GET (stype, flag, ctype) \ + FB_PROPERTY_SET (stype, flag, mutator, ctype, __VA_ARGS__) + +#define DEFINE_RW_PROPERTY_OBJ(stype, flag, mutator, ctype, ...) \ + FB_PROPERTY_GET (stype, flag, ctype) \ + FB_PROPERTY_SET (stype, flag, mutator, ctype, __VA_ARGS__) + +#define DEFINE_RW_PROPERTY_OBJ_COPY(stype, flag, mutator, ctype, ...) \ + FB_PROPERTY_GET (stype, flag, ctype) \ + FB_PROPERTY_SET_OBJ_COPY (stype, flag, mutator, ctype, __VA_ARGS__) + + +/** + Internal delegate definition. + */ +@interface NSObject (POPAnimationDelegateInternal) +- (void)pop_animation:(POPAnimation *)anim didReachProgress:(CGFloat)progress; +@end + +struct _POPAnimationState +{ + id __unsafe_unretained self; + POPAnimationType type; + NSString *name; + NSUInteger ID; + CFTimeInterval beginTime; + CFTimeInterval startTime; + CFTimeInterval lastTime; + id __weak delegate; + POPAnimationCompletionBlock completionBlock; + NSMutableDictionary *dict; + POPAnimationTracer *tracer; + CGFloat progress; + + bool active:1; + bool paused:1; + bool removedOnCompletion:1; + + bool delegateDidStart:1; + bool delegateDidStop:1; + bool delegateDidProgress:1; + bool delegateDidApply:1; + bool delegateDidReachToValue:1; + + bool additive:1; + bool didReachToValue:1; + bool tracing:1; // corresponds to tracer started + bool userSpecifiedDynamics:1; + bool customFinished:1; + + _POPAnimationState(id __unsafe_unretained anim) : + self(anim), + type((POPAnimationType)0), + name(nil), + ID(0), + beginTime(0), + startTime(0), + lastTime(0), + delegate(nil), + completionBlock(nil), + dict(nil), + tracer(nil), + progress(0), + active(false), + paused(true), + removedOnCompletion(true), + delegateDidStart(false), + delegateDidStop(false), + delegateDidProgress(false), + delegateDidApply(false), + delegateDidReachToValue(false), + additive(false), + didReachToValue(false), + tracing(false), + userSpecifiedDynamics(false), + customFinished(false) {} + + virtual ~_POPAnimationState() + { + name = nil; + dict = nil; + tracer = nil; + completionBlock = NULL; + } + + bool isCustom() { + return kPOPAnimationCustom == type; + } + + bool isStarted() { + return 0 != startTime; + } + + id getDelegate() { + return delegate; + } + + void setDelegate(id d) { + if (d != delegate) { + delegate = d; + delegateDidStart = [d respondsToSelector:@selector(pop_animationDidStart:)]; + delegateDidStop = [d respondsToSelector:@selector(pop_animationDidStop:finished:)]; + delegateDidProgress = [d respondsToSelector:@selector(pop_animation:didReachProgress:)]; + delegateDidApply = [d respondsToSelector:@selector(pop_animationDidApply:)]; + delegateDidReachToValue = [d respondsToSelector:@selector(pop_animationDidReachToValue:)]; + } + } + + bool getPaused() { + return paused; + } + + void setPaused(bool f) { + if (f != paused) { + paused = f; + if (!paused) { + reset(false); + } + } + } + + CGFloat getProgress() { + return progress; + } + + /* returns true if started */ + bool startIfNeeded(id obj, CFTimeInterval time, CFTimeInterval offset) + { + bool started = false; + + // detect start based on time + if (0 == startTime && time >= beginTime + offset) { + + // activate & unpause + active = true; + setPaused(false); + + // start us one frame in the past (when we added the animation) + if (0 == beginTime) { + time -= 1/60.; + } + + // note start time + startTime = lastTime = time; + started = true; + } + + // ensure values for running animation + bool running = active && !paused; + if (running) { + willRun(started, obj); + } + + // handle start + if (started) { + handleDidStart(); + } + + return started; + } + + void stop(bool removing, bool done) { + if (active) + { + // delegate progress one last time + if (done) { + delegateProgress(); + } + + if (removing) { + active = false; + } + + handleDidStop(done); + } else { + + // stopped before even started + // delegate start and stop regardless; matches CA behavior + if (!isStarted()) { + handleDidStart(); + handleDidStop(false); + } + } + + setPaused(true); + } + + virtual void handleDidStart() + { + if (delegateDidStart) { + ActionEnabler enabler; + [delegate pop_animationDidStart:self]; + } + + if (tracing) { + [tracer didStart]; + } + } + + void handleDidStop(BOOL done) + { + if (delegateDidStop) { + ActionEnabler enabler; + [delegate pop_animationDidStop:self finished:done]; + } + + // add another strong reference to completion block before callout + POPAnimationCompletionBlock block = completionBlock; + if (block != NULL) { + ActionEnabler enabler; + block(self, done); + } + + if (tracing) { + [tracer didStop:done]; + } + } + + /* virtual functions */ + virtual bool isDone() { + if (isCustom()) { + return customFinished; + } + + return false; + } + + bool advanceTime(CFTimeInterval time, id obj) { + bool advanced = false; + bool computedProgress = false; + + CFTimeInterval dt = time - lastTime; + if (dt < 0.001) + return advanced; + + switch (type) { + case kPOPAnimationSpring: + advanced = advance(time, dt, obj); + break; + case kPOPAnimationDecay: + advanced = advance(time, dt, obj); + break; + case kPOPAnimationBasic: { + advanced = advance(time, dt, obj); + computedProgress = true; + break; + } + case kPOPAnimationCustom: { + customFinished = [self _advance:obj currentTime:time elapsedTime:dt] ? false : true; + advanced = true; + break; + } + default: + break; + } + + if (advanced) { + + // estimate progress + if (!computedProgress) { + computeProgress(); + } + + // delegate progress + delegateProgress(); + + // update time + lastTime = time; + } + + return advanced; + } + + virtual void willRun(bool started, id obj) {} + virtual bool advance(CFTimeInterval time, CFTimeInterval dt, id obj) { return false; } + virtual void computeProgress() {} + virtual void delegateProgress() {} + + virtual void delegateApply() { + if (delegateDidApply) { + ActionEnabler enabler; + [delegate pop_animationDidApply:self]; + } + } + + virtual void reset(bool all) { + startTime = 0; + lastTime = 0; + } +}; + +typedef struct _POPAnimationState POPAnimationState; + + +@interface POPAnimation () +{ +@protected + struct _POPAnimationState *_state; +} + +@end + +// NSProxy extensions, for testing pursposes +@interface NSProxy (POP) +- (void)pop_addAnimation:(POPAnimation *)anim forKey:(NSString *)key; +- (void)pop_removeAllAnimations; +- (void)pop_removeAnimationForKey:(NSString *)key; +- (NSArray *)pop_animationKeys; +- (POPAnimation *)pop_animationForKey:(NSString *)key; +@end diff --git a/pop/POPAnimationPrivate.h b/pop/POPAnimationPrivate.h new file mode 100644 index 00000000..0b7189f0 --- /dev/null +++ b/pop/POPAnimationPrivate.h @@ -0,0 +1,16 @@ +/** + 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 + +#define POP_ANIMATION_FRICTION_FOR_QC_FRICTION(qcFriction) (25.0 + (((qcFriction - 8.0) / 2.0) * (25.0 - 19.0))) +#define POP_ANIMATION_TENSION_FOR_QC_TENSION(qcTension) (194.0 + (((qcTension - 30.0) / 50.0) * (375.0 - 194.0))) + +#define QC_FRICTION_FOR_POP_ANIMATION_FRICTION(fbFriction) (8.0 + 2.0 * ((fbFriction - 25.0)/(25.0 - 19.0))) +#define QC_TENSION_FOR_POP_ANIMATION_TENSION(fbTension) (30.0 + 50.0 * ((fbTension - 194.0)/(375.0 - 194.0))) diff --git a/pop/POPAnimationRuntime.h b/pop/POPAnimationRuntime.h new file mode 100644 index 00000000..bc02e9b5 --- /dev/null +++ b/pop/POPAnimationRuntime.h @@ -0,0 +1,102 @@ +/** + 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 +#include + +#import + +#import "POPMath.h" +#import "POPVector.h" + +enum POPValueType +{ + kPOPValueUnknown = 0, + kPOPValueInteger, + kPOPValueFloat, + kPOPValuePoint, + kPOPValueSize, + kPOPValueRect, + kPOPValueAffineTransform, + kPOPValueTransform, + kPOPValueRange, + kPOPValueColor, +}; + +using namespace POP; + +/** + Returns value type based on objc type description, given list of supported value types and length. + */ +extern POPValueType POPSelectValueType(const char *objctype, const POPValueType *types, size_t length); + +/** + Returns value type based on objc object, given a list of supported value types and length. + */ +extern POPValueType POPSelectValueType(id obj, const POPValueType *types, size_t length); + +/** + Array of all value types. + */ +extern const POPValueType kPOPAnimatableAllTypes[9]; + +/** + Array of all value types supported for animation. + */ +extern const POPValueType kPOPAnimatableSupportTypes[7]; + +/** + Returns a string description of a value type. + */ +extern NSString *POPValueTypeToString(POPValueType t); + +/** + Returns a mutable dictionary of weak pointer keys to weak pointer values. + */ +extern CFMutableDictionaryRef POPDictionaryCreateMutableWeakPointerToWeakPointer(NSUInteger capacity) CF_RETURNS_RETAINED; + +/** + Returns a mutable dictionary of weak pointer keys to weak pointer values. + */ +extern CFMutableDictionaryRef POPDictionaryCreateMutableWeakPointerToStrongObject(NSUInteger capacity) CF_RETURNS_RETAINED; + +/** + Box a vector. + */ +extern id POPBox(VectorConstRef vec, POPValueType type, bool force = false); + +/** + Unbox a vector. + */ +extern VectorRef POPUnbox(id value, POPValueType &type, NSUInteger &count, bool validate); + +/** + Read/write block typedefs for convenience. + */ +typedef void(^pop_animatable_read_block)(id obj, CGFloat *value); +typedef void(^pop_animatable_write_block)(id obj, const CGFloat *value); + +/** + Read object value and return a Vector4r. + */ +NS_INLINE Vector4r read_values(pop_animatable_read_block read, id obj, size_t count) +{ + Vector4r vec = Vector4r::Zero(); + if (0 == count) + return vec; + + read(obj, vec.data()); + + return vec; +} + +NS_INLINE NSString *POPStringFromBOOL(BOOL value) +{ + return value ? @"YES" : @"NO"; +} diff --git a/pop/POPAnimationRuntime.mm b/pop/POPAnimationRuntime.mm new file mode 100644 index 00000000..eb3b5689 --- /dev/null +++ b/pop/POPAnimationRuntime.mm @@ -0,0 +1,270 @@ +/** + 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 "POPAnimationRuntime.h" + +#import + +#import + +#if TARGET_OS_IPHONE +#import +#else +#import +#endif + +#import "POPVector.h" +#import "POPAnimationRuntime.h" +#import "POPGeometry.h" + +static Boolean pointerEqual(const void *ptr1, const void *ptr2) { + return ptr1 == ptr2; +} + +static CFHashCode pointerHash(const void *ptr) { + return (CFHashCode)(ptr); +} + +CFMutableDictionaryRef POPDictionaryCreateMutableWeakPointerToWeakPointer(NSUInteger capacity) +{ + CFDictionaryKeyCallBacks kcb = kCFTypeDictionaryKeyCallBacks; + + // weak, pointer keys + kcb.retain = NULL; + kcb.retain = NULL; + kcb.equal = pointerEqual; + kcb.hash = pointerHash; + + CFDictionaryValueCallBacks vcb = kCFTypeDictionaryValueCallBacks; + + // weak, pointer values + vcb.retain = NULL; + vcb.release = NULL; + vcb.equal = pointerEqual; + + return CFDictionaryCreateMutable(NULL, capacity, &kcb, &vcb); +} + +CFMutableDictionaryRef POPDictionaryCreateMutableWeakPointerToStrongObject(NSUInteger capacity) +{ + CFDictionaryKeyCallBacks kcb = kCFTypeDictionaryKeyCallBacks; + + // weak, pointer keys + kcb.retain = NULL; + kcb.release = NULL; + kcb.equal = pointerEqual; + kcb.hash = pointerHash; + + // strong, object values + CFDictionaryValueCallBacks vcb = kCFTypeDictionaryValueCallBacks; + + return CFDictionaryCreateMutable(NULL, capacity, &kcb, &vcb); +} + +static bool FBCompareTypeEncoding(const char *objctype, POPValueType type) +{ + switch (type) + { + case kPOPValueFloat: + return (strcmp(objctype, @encode(float)) == 0 + || strcmp(objctype, @encode(double)) == 0 + ); + + case kPOPValuePoint: + return (strcmp(objctype, @encode(CGPoint)) == 0 +#if !TARGET_OS_IPHONE + || strcmp(objctype, @encode(NSPoint)) == 0 +#endif + ); + + case kPOPValueSize: + return (strcmp(objctype, @encode(CGSize)) == 0 +#if !TARGET_OS_IPHONE + || strcmp(objctype, @encode(NSSize)) == 0 +#endif + ); + + case kPOPValueRect: + return (strcmp(objctype, @encode(CGRect)) == 0 +#if !TARGET_OS_IPHONE + || strcmp(objctype, @encode(NSRect)) == 0 +#endif + ); + + case kPOPValueAffineTransform: + return strcmp(objctype, @encode(CGAffineTransform)) == 0; + + case kPOPValueTransform: + return strcmp(objctype, @encode(CATransform3D)) == 0; + + case kPOPValueRange: + return strcmp(objctype, @encode(CFRange)) == 0 + || strcmp(objctype, @encode (NSRange)) == 0; + + case kPOPValueInteger: + return (strcmp(objctype, @encode(int)) == 0 + || strcmp(objctype, @encode(unsigned int)) == 0 + || strcmp(objctype, @encode(short)) == 0 + || strcmp(objctype, @encode(unsigned short)) == 0 + || strcmp(objctype, @encode(long)) == 0 + || strcmp(objctype, @encode(unsigned long)) == 0 + || strcmp(objctype, @encode(long long)) == 0 + || strcmp(objctype, @encode(unsigned long long)) == 0 + ); + default: + return false; + } +} + +POPValueType POPSelectValueType(const char *objctype, const POPValueType *types, size_t length) +{ + if (NULL != objctype) { + for (size_t idx = 0; idx < length; idx++) { + if (FBCompareTypeEncoding(objctype, types[idx])) + return types[idx]; + } + } + return kPOPValueUnknown; +} + +POPValueType POPSelectValueType(id obj, const POPValueType *types, size_t length) +{ + if ([obj isKindOfClass:[NSValue class]]) { + return POPSelectValueType([obj objCType], types, length); + } else if (CFGetTypeID((__bridge CFTypeRef)obj) == CGColorGetTypeID()) { + return kPOPValueColor; + } + return kPOPValueUnknown; +} + +const POPValueType kPOPAnimatableAllTypes[9] = {kPOPValueInteger, kPOPValueFloat, kPOPValuePoint, kPOPValueSize, kPOPValueRect, kPOPValueAffineTransform, kPOPValueTransform, kPOPValueRange, kPOPValueColor}; + +const POPValueType kPOPAnimatableSupportTypes[7] = {kPOPValueInteger, kPOPValueFloat, kPOPValuePoint, kPOPValueSize, kPOPValueRect, kPOPValueColor}; + +NSString *POPValueTypeToString(POPValueType t) +{ + switch (t) { + case kPOPValueUnknown: + return @"unknown"; + case kPOPValueInteger: + return @"int"; + case kPOPValueFloat: + return @"CGFloat"; + case kPOPValuePoint: + return @"CGPoint"; + case kPOPValueSize: + return @"CGSize"; + case kPOPValueRect: + return @"CGRect"; + case kPOPValueAffineTransform: + return @"CGAffineTransform"; + case kPOPValueTransform: + return @"CATransform3D"; + case kPOPValueRange: + return @"CFRange"; + case kPOPValueColor: + return @"CGColorRef"; + default: + return nil; + } +} + +id POPBox(VectorConstRef vec, POPValueType type, bool force) +{ + if (NULL == vec) + return nil; + + switch (type) { + case kPOPValueInteger: + case kPOPValueFloat: + return @(vec->data()[0]); + break; + case kPOPValuePoint: + return [NSValue valueWithCGPoint:vec->cg_point()]; + break; + case kPOPValueSize: + return [NSValue valueWithCGSize:vec->cg_size()]; + break; + case kPOPValueRect: + return [NSValue valueWithCGRect:vec->cg_rect()]; + break; + case kPOPValueColor: { + return (__bridge_transfer id)vec->cg_color(); + break; + } + default: + return force ? [NSValue valueWithCGPoint:vec->cg_point()] : nil; + break; + } +} + +static VectorRef vectorize(id value, POPValueType type) +{ + Vector *vec = NULL; + + switch (type) { + case kPOPValueInteger: + case kPOPValueFloat: + vec = Vector::new_cg_float([value floatValue]); + break; + case kPOPValuePoint: + vec = Vector::new_cg_point([value CGPointValue]); + break; + case kPOPValueSize: + vec = Vector::new_cg_size([value CGSizeValue]); + break; + case kPOPValueRect: + vec = Vector::new_cg_rect([value CGRectValue]); + break; + case kPOPValueAffineTransform: + vec = Vector::new_cg_affine_transform([value CGAffineTransformValue]); + break; + case kPOPValueColor: + vec = Vector::new_cg_color((__bridge CGColorRef)value); + default: + break; + } + + return VectorRef(vec); +} + +VectorRef POPUnbox(id value, POPValueType &animationType, NSUInteger &count, bool validate) +{ + if (nil == value) { + count = 0; + return VectorRef(NULL); + } + + // determine type of value + POPValueType valueType = POPSelectValueType(value, kPOPAnimatableSupportTypes, POP_ARRAY_COUNT(kPOPAnimatableSupportTypes)); + + // handle unknown types + if (kPOPValueUnknown == valueType) { + NSString *valueDesc = kPOPValueUnknown != valueType ? POPValueTypeToString(valueType) : [[value class] description]; + [NSException raise:@"Unsuported value" format:@"Animating %@ values is not supported", valueDesc]; + } + + // vectorize + VectorRef vec = vectorize(value, valueType); + + if (kPOPValueUnknown == animationType || 0 == count) { + // update animation type based on value type + animationType = valueType; + if (NULL != vec) { + count = vec->size(); + } + } else if (validate) { + // allow for mismatched types, so long as vector size matches + if (count != vec->size()) { + [NSException raise:@"Invalid value" format:@"%@ should be of type %@", value, POPValueTypeToString(animationType)]; + } + } + + return vec; +} diff --git a/pop/POPAnimationTracer.h b/pop/POPAnimationTracer.h new file mode 100644 index 00000000..53a6a03d --- /dev/null +++ b/pop/POPAnimationTracer.h @@ -0,0 +1,60 @@ +/** + 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 + +#import + +@class POPAnimation; + +/** + @abstract Tracer of animation events to fasciliate unit testing & debugging. + */ +@interface POPAnimationTracer : NSObject + +/** + @abstract Start recording events. + */ +- (void)start; + +/** + @abstract Stop recording events. + */ +- (void)stop; + +/** + @abstract Resets any recoded events. Continues recording events if already started. + */ +- (void)reset; + +/** + @abstract Property representing all recorded events. + @discussion Events are returned in order of occurence. + */ +@property (nonatomic, assign, readonly) NSArray *allEvents; + +/** + @abstract Property representing all recorded write events for convenience. + @discussion Events are returned in order of occurence. + */ +@property (nonatomic, assign, readonly) NSArray *writeEvents; + +/** + @abstract Queries for events of specified type. + @param type The type of event to return. + @returns An array of events of specified type in order of occurence. + */ +- (NSArray *)eventsWithType:(POPAnimationEventType)type; + +/** + @abstract Property indicating whether tracer should automatically log events and reset collection on animation completion. + */ +@property (nonatomic, assign) BOOL shouldLogAndResetOnCompletion; + +@end diff --git a/pop/POPAnimationTracer.mm b/pop/POPAnimationTracer.mm new file mode 100644 index 00000000..e2ebfb47 --- /dev/null +++ b/pop/POPAnimationTracer.mm @@ -0,0 +1,184 @@ +/** + 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 "POPAnimationTracer.h" + +#import + +#import "POPAnimationEventInternal.h" +#import "POPAnimationInternal.h" +#import "POPSpringAnimation.h" + +@implementation POPAnimationTracer +{ + __weak POPAnimation *_animation; + POPAnimationState *_animationState; + NSMutableArray *_events; + BOOL _animationHasVelocity; +} + +static POPAnimationEvent *create_event(POPAnimationTracer *self, POPAnimationEventType type, id value = nil, bool recordAnimation = false) +{ + bool useLocalTime = 0 != self->_animationState->startTime; + CFTimeInterval time = useLocalTime + ? self->_animationState->lastTime - self->_animationState->startTime + : self->_animationState->lastTime; + + POPAnimationEvent *event; + + if (!value) { + event = [[POPAnimationEvent alloc] initWithType:type time:time]; + } else { + event = [[POPAnimationValueEvent alloc] initWithType:type time:time value:value]; + if (self->_animationHasVelocity) { + [(POPAnimationValueEvent *)event setVelocity:[(POPSpringAnimation *)self->_animation velocity]]; + } + } + + if (recordAnimation) { + event.animationDescription = [self->_animation description]; + } + + return event; +} + +- (id)initWithAnimation:(POPAnimation *)anAnim +{ + self = [super init]; + if (nil != self) { + _animation = anAnim; + _animationState = POPAnimationGetState(anAnim); + _events = [[NSMutableArray alloc] initWithCapacity:50]; + _animationHasVelocity = [anAnim respondsToSelector:@selector(velocity)]; + } + return self; +} + +- (void)readPropertyValue:(id)aValue +{ + POPAnimationEvent *event = create_event(self, kPOPAnimationEventPropertyRead, aValue); + [_events addObject:event]; +} + +- (void)writePropertyValue:(id)aValue +{ + POPAnimationEvent *event = create_event(self, kPOPAnimationEventPropertyWrite, aValue); + [_events addObject:event]; +} + +- (void)updateToValue:(id)aValue +{ + POPAnimationEvent *event = create_event(self, kPOPAnimationEventToValueUpdate, aValue); + [_events addObject:event]; +} + +- (void)updateFromValue:(id)aValue +{ + POPAnimationEvent *event = create_event(self, kPOPAnimationEventFromValueUpdate, aValue); + [_events addObject:event]; +} + +- (void)updateVelocity:(id)aValue +{ + POPAnimationEvent *event = create_event(self, kPOPAnimationEventVelocityUpdate, aValue); + [_events addObject:event]; +} + +- (void)updateSpeed:(float)aFloat +{ + POPAnimationEvent *event = create_event(self, kPOPAnimationEventSpeedUpdate, @(aFloat)); + [_events addObject:event]; +} + +- (void)updateBounciness:(float)aFloat +{ + POPAnimationEvent *event = create_event(self, kPOPAnimationEventBouncinessUpdate, @(aFloat)); + [_events addObject:event]; +} + +- (void)updateFriction:(float)aFloat +{ + POPAnimationEvent *event = create_event(self, kPOPAnimationEventFrictionUpdate, @(aFloat)); + [_events addObject:event]; +} + +- (void)updateMass:(float)aFloat +{ + POPAnimationEvent *event = create_event(self, kPOPAnimationEventMassUpdate, @(aFloat)); + [_events addObject:event]; +} + +- (void)updateTension:(float)aFloat +{ + POPAnimationEvent *event = create_event(self, kPOPAnimationEventTensionUpdate, @(aFloat)); + [_events addObject:event]; +} + +- (void)didStart +{ + POPAnimationEvent *event = create_event(self, kPOPAnimationEventDidStart, nil, true); + [_events addObject:event]; +} + +- (void)didStop:(BOOL)finished +{ + POPAnimationEvent *event = create_event(self, kPOPAnimationEventDidStop, @(finished), true); + [_events addObject:event]; + + if (_shouldLogAndResetOnCompletion) { + NSLog(@"events:%@", self.allEvents); + [self reset]; + } +} + +- (void)didReachToValue:(id)aValue +{ + POPAnimationEvent *event = create_event(self, kPOPAnimationEventDidReachToValue, aValue); + [_events addObject:event]; +} + +- (void)start +{ + POPAnimationState *s = POPAnimationGetState(_animation); + s->tracing = true; +} + +- (void)stop +{ + POPAnimationState *s = POPAnimationGetState(_animation); + s->tracing = false; +} + +- (void)reset +{ + [_events removeAllObjects]; +} + +- (NSArray *)allEvents +{ + return [_events copy]; +} + +- (NSArray *)writeEvents +{ + return [self eventsWithType:kPOPAnimationEventPropertyWrite]; +} + +- (NSArray *)eventsWithType:(POPAnimationEventType)aType +{ + NSMutableArray *array = [NSMutableArray array]; + for (POPAnimationEvent *event in _events) { + if (aType == event.type) { + [array addObject:event]; + } + } + return array; +} + +@end diff --git a/pop/POPAnimationTracerInternal.h b/pop/POPAnimationTracerInternal.h new file mode 100644 index 00000000..748b9954 --- /dev/null +++ b/pop/POPAnimationTracerInternal.h @@ -0,0 +1,91 @@ +/** + 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 + +#import + +@interface POPAnimationTracer (Internal) + +/** + @abstract Designated initalizer. Pass the animation being traced. + */ +- (instancetype)initWithAnimation:(POPAnimation *)anAnim; + +/** + @abstract Records read value. + */ +- (void)readPropertyValue:(id)aValue; + +/** + @abstract Records write value. + */ +- (void)writePropertyValue:(id)aValue; + +/** + Records to value update. + */ +- (void)updateToValue:(id)aValue; + +/** + @abstract Records from value update. + */ +- (void)updateFromValue:(id)aValue; + +/** + @abstract Records from value update. + */ +- (void)updateVelocity:(id)aValue; + +/** + @abstract Records bounciness update. + */ +- (void)updateBounciness:(float)aFloat; + +/** + @abstract Records speed update. + */ +- (void)updateSpeed:(float)aFloat; + +/** + @abstract Records friction update. + */ +- (void)updateFriction:(float)aFloat; + +/** + @abstract Records mass update. + */ +- (void)updateMass:(float)aFloat; + +/** + @abstract Records tension update. + */ +- (void)updateTension:(float)aFloat; + +/** + @abstract Records did add. + */ +- (void)didAdd; + +/** + @abstract Records did start. + */ +- (void)didStart; + +/** + @abstract Records did stop. + */ +- (void)didStop:(BOOL)finished; + +/** + @abstract Records did reach to value. + */ +- (void)didReachToValue:(id)aValue; + +@end diff --git a/pop/POPAnimator.h b/pop/POPAnimator.h new file mode 100644 index 00000000..7d71d248 --- /dev/null +++ b/pop/POPAnimator.h @@ -0,0 +1,47 @@ +/** + 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 + +@protocol POPAnimatorDelegate; + +/** + @abstract The animator class renders animations. + */ +@interface POPAnimator : NSObject + +/** + @abstract The shared animator instance. + @discussion Consumers should generally use the shared instance in lieu of creating new instances. + */ ++ (instancetype)sharedAnimator; + +/** + @abstract The optional animator delegate. + */ +@property (weak, nonatomic) id delegate; + +@end + +/** + @abstract The animator delegate. + */ +@protocol POPAnimatorDelegate + +/** + @abstract Called on each frame before animation application. + */ +- (void)animatorWillAnimate:(POPAnimator *)animator; + +/** + @abstract Called on each frame after animation application. + */ +- (void)animatorDidAnimate:(POPAnimator *)animator; + +@end diff --git a/pop/POPAnimator.mm b/pop/POPAnimator.mm new file mode 100644 index 00000000..1eab8a57 --- /dev/null +++ b/pop/POPAnimator.mm @@ -0,0 +1,536 @@ +/** + 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 "POPAnimator.h" +#import "POPAnimatorPrivate.h" + +#import +#import +#import + +#import + +#import "POPAnimation.h" +#import "POPAnimationExtras.h" +#import "POPAnimationInternal.h" +#import "POPAnimationRuntime.h" +#import "POPBasicAnimationInternal.h" +#import "POPDecayAnimationInternal.h" +#import "POPSpringAnimationInternal.h" +#import "POPSpringSolver.h" + +using namespace std; +using namespace POP; + +#define ENABLE_LOGGING_DEBUG 0 +#define ENABLE_LOGGING_INFO 0 + +#if ENABLE_LOGGING_DEBUG +#define FBLogAnimDebug NSLog +#else +#define FBLogAnimDebug(...) +#endif + +#if ENABLE_LOGGING_INFO +#define FBLogAnimInfo NSLog +#else +#define FBLogAnimInfo(...) +#endif + +class POPAnimatorItem +{ +public: + id __weak object; + NSString *key; + POPAnimation *animation; + NSInteger refCount; + id __unsafe_unretained unretainedObject; + + POPAnimatorItem(id o, NSString *k, POPAnimation *a) POP_NOTHROW + { + object = o; + key = [k copy]; + animation = a; + refCount = 1; + unretainedObject = o; + } + + ~POPAnimatorItem() + { + } + + bool operator==(const POPAnimatorItem& o) const { + return unretainedObject == o.unretainedObject && animation == o.animation && [key isEqualToString:o.key]; + } + +}; + +typedef std::shared_ptr POPAnimatorItemRef; +typedef std::shared_ptr POPAnimatorItemConstRef; + +typedef std::list POPAnimatorItemList; +typedef POPAnimatorItemList::iterator POPAnimatorItemListIterator; +typedef POPAnimatorItemList::const_iterator POPAnimatorItemListConstIterator; + +static BOOL _disableBackgroundThread = YES; + +@interface POPAnimator () +{ +#if TARGET_OS_IPHONE + CADisplayLink *_displayLink; +#else + CVDisplayLinkRef _displayLink; +#endif + POPAnimatorItemList _list; + CFMutableDictionaryRef _dict; + NSMutableSet *_observers; + CFTimeInterval _slowMotionStartTime; + CFTimeInterval _slowMotionLastTime; + CFTimeInterval _slowMotionAccumulator; +} +@end + +@implementation POPAnimator + +#if !TARGET_OS_IPHONE +static CVReturn displayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *context) +{ + if (_disableBackgroundThread) { + dispatch_async(dispatch_get_main_queue(), ^{ + [(__bridge POPAnimator*)context render]; + }); + } else { + [(__bridge POPAnimator*)context render]; + } + return kCVReturnSuccess; +} +#endif + +static void updateAnimating(POPAnimator *self) +{ + BOOL paused = 0 == self->_observers.count && self->_list.empty(); + +#if TARGET_OS_IPHONE + if (paused != self->_displayLink.paused) { + FBLogAnimInfo(paused ? @"pausing display link" : @"unpausing display link"); + self->_displayLink.paused = paused; + } +#else + if (paused == CVDisplayLinkIsRunning(self->_displayLink)) { + FBLogAnimInfo(paused ? @"pausing display link" : @"unpausing display link"); + if (paused) { + CVDisplayLinkStop(self->_displayLink); + } else { + CVDisplayLinkStart(self->_displayLink); + } + } +#endif +} + +static void updateAnimatable(id obj, POPPropertyAnimationState *anim) +{ + // handle user-initiated stop or pause; hault animation + if (!anim->active || anim->paused) + return; + + if (anim->hasValue()) { + pop_animatable_write_block write = anim->property.writeBlock; + if (NULL == write) + return; + + if (!anim->additive) { + + VectorRef currentVec = anim->currentValue(); + + // update previous values; support animation convergence + anim->previous2Vec = anim->previousVec; + anim->previousVec = currentVec; + + // write value + write(obj, currentVec->data()); + if (anim->tracing) { + [anim->tracer writePropertyValue:POPBox(currentVec, anim->valueType, true)]; + } + } else { + pop_animatable_read_block read = anim->property.readBlock; + if (NULL == read) + return; + + // object value + Vector4r objectValue = read_values(read, obj, anim->valueCount); + + // current animation value + VectorRef currentVec = anim->currentValue(); + Vector4r currentValue = currentVec->vector4r(); + + // determine animation change + if (anim->previousVec) { + Vector4r previousValue = anim->previousVec->vector4r(); + currentValue -= previousValue; + } + + // add to object value + currentValue += objectValue; + + // update previous values; support animation convergence + anim->previous2Vec = anim->previousVec; + anim->previousVec = currentVec; + + // write value + write(obj, currentValue.data()); + if (anim->tracing) { + [anim->tracer writePropertyValue:POPBox(currentVec, anim->valueType, true)]; + } + } + } +} + +static void applyAnimationTime(id obj, POPAnimationState *state, CFTimeInterval time) +{ + if (!state->advanceTime(time, obj)) { + return; + } + + POPPropertyAnimationState *ps = dynamic_cast(state); + if (NULL != ps) { + updateAnimatable(obj, ps); + } + + state->delegateApply(); +} + +static void applyAnimationProgress(id obj, POPAnimationState *state, CGFloat progress) +{ + POPPropertyAnimationState *ps = dynamic_cast(state); + if (ps && !ps->advanceProgress(progress)) { + return; + } + + if (NULL != ps) { + updateAnimatable(obj, ps); + } + + state->delegateApply(); +} + +static POPAnimation *deleteDictEntry(POPAnimator *self, id __unsafe_unretained obj, NSString *key, BOOL cleanup = YES) +{ + NSMutableDictionary *animations = (__bridge id)CFDictionaryGetValue(self->_dict, (__bridge void *)obj); + if (nil == animations) + return nil; + + POPAnimation *anim = animations[key]; + if (nil == anim) + return nil; + + // remove key + [animations removeObjectForKey:key]; + + // cleanup empty dictionaries + if (cleanup && 0 == animations.count) + CFDictionaryRemoveValue(self->_dict, (__bridge void *)obj); + + return anim; +} + +static void stopAndCleanup(POPAnimator *self, POPAnimatorItemRef item, bool shouldRemove, bool finished) +{ + // remove + if (shouldRemove) { + deleteDictEntry(self, item->unretainedObject, item->key); + } + + // stop + POPAnimationState *state = POPAnimationGetState(item->animation); + state->stop(shouldRemove, finished); + + if (shouldRemove) { + // find item im list + // may have already been removed on animationDidStop: + POPAnimatorItemListIterator find_iter = find(self->_list.begin(), self->_list.end(), item); + BOOL found = find_iter != self->_list.end(); + + if (found) { + self->_list.erase(find_iter); + } + } +} + ++ (id)sharedAnimator +{ + static POPAnimator* _animator = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _animator = [[POPAnimator alloc] init]; + }); + return _animator; +} + ++ (BOOL)disableBackgroundThread +{ + return _disableBackgroundThread; +} + ++ (void)setDisableBackgroundThread:(BOOL)flag +{ + _disableBackgroundThread = flag; +} + +- (id)init +{ + self = [super init]; + if (nil == self) return nil; + +#if TARGET_OS_IPHONE + _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render)]; + _displayLink.paused = YES; + [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; +#else + CVReturn ret = CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink); + ret = CVDisplayLinkSetOutputCallback(_displayLink, displayLinkCallback, (__bridge void *)self); +#endif + + _dict = POPDictionaryCreateMutableWeakPointerToStrongObject(5); + + return self; +} + +- (void)dealloc +{ +#if TARGET_OS_IPHONE + [_displayLink invalidate]; +#else + CVDisplayLinkStop(_displayLink); + CVDisplayLinkRelease(_displayLink); +#endif +} + +- (void)addAnimation:(POPAnimation *)anim forObject:(id)obj key:(NSString *)key +{ + if (!anim || !obj) { + return; + } + + // support arbitrarily many nil keys + if (!key) { + key = [[NSUUID UUID] UUIDString]; + } + + NSMutableDictionary *animations = (__bridge id)CFDictionaryGetValue(_dict, (__bridge void *)obj); + + // update associated animation state + if (nil == animations) { + animations = [NSMutableDictionary dictionary]; + CFDictionarySetValue(_dict, (__bridge void *)obj, (__bridge void *)animations); + } else { + // if the animation instance already exists, avoid cancelling only to restart + POPAnimation *existingAnim = animations[key]; + if (existingAnim) { + if (existingAnim == anim) { + return; + } + [self removeAnimationForObject:obj key:key cleanupDict:NO]; + } + } + animations[key] = anim; + + // create entry after potential removal + POPAnimatorItemRef item(new POPAnimatorItem(obj, key, anim)); + _list.push_back(item); + + // support animation re-use, reset all animation state + POPAnimationGetState(anim)->reset(true); + + // start animating if necessary + updateAnimating(self); +} + +- (void)removeAllAnimationsForObject:(id)obj +{ + NSArray *animations = [(__bridge id)CFDictionaryGetValue(_dict, (__bridge void *)obj) allValues]; + CFDictionaryRemoveValue(_dict, (__bridge void *)obj); + + if (0 == animations.count) + return; + + NSHashTable *animationSet = [[NSHashTable alloc] initWithOptions:NSHashTableObjectPointerPersonality capacity:animations.count]; + for (id animation in animations) { + [animationSet addObject:animation]; + } + + POPAnimatorItemRef item; + for (auto iter = _list.begin(); iter != _list.end();) { + item = *iter; + if(![animationSet containsObject:item->animation]) { + iter++; + } else { + POPAnimationState *state = POPAnimationGetState(item->animation); + state->stop(true, !state->active); + iter = _list.erase(iter); + } + } + + + for (POPAnimation *anim in animations) { + POPAnimationState *state = POPAnimationGetState(anim); + state->stop(true, !state->active); + } +} + +- (void)removeAnimationForObject:(id)obj key:(NSString *)key cleanupDict:(BOOL)cleanupDict +{ + POPAnimation *anim = deleteDictEntry(self, obj, key, cleanupDict); + if (nil == anim) + return; + + POPAnimatorItemRef item; + for (auto iter = _list.begin(); iter != _list.end();) { + item = *iter; + if(anim == item->animation) { + POPAnimationState *state = POPAnimationGetState(item->animation); + state->stop(true, (!state->active && !state->paused)); + iter = _list.erase(iter); + break; + } else { + iter++; + } + } +} + +- (void)removeAnimationForObject:(id)obj key:(NSString *)key +{ + [self removeAnimationForObject:obj key:key cleanupDict:YES]; +} + +- (NSArray *)animationKeysForObject:(id)obj +{ + NSArray *keys = [(__bridge id)CFDictionaryGetValue(_dict, (__bridge void *)obj) allKeys]; + return keys; +} + +- (id)animationForObject:(id)obj key:(NSString *)key +{ + NSDictionary *animations = (__bridge id)CFDictionaryGetValue(_dict, (__bridge void *)obj); + return animations[key]; +} + +- (void)render +{ + CFTimeInterval time = CACurrentMediaTime(); + +#if TARGET_IPHONE_SIMULATOR + // support slow-motion animations + time += _slowMotionAccumulator; + float f = POPAnimationDragCoefficient(); + + if (f > 1.0) { + if (!_slowMotionStartTime) { + _slowMotionStartTime = time; + } else { + time = (time - _slowMotionStartTime) / f + _slowMotionStartTime; + _slowMotionLastTime = time; + } + } else if (_slowMotionStartTime) { + CFTimeInterval dt = (_slowMotionLastTime - time); + time += dt; + _slowMotionAccumulator += dt; + _slowMotionStartTime = 0; + } +#endif + + [self renderTime:time]; +} + +- (void)renderTime:(CFTimeInterval)time +{ + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + + [_delegate animatorWillAnimate:self]; + + const NSUInteger count = _list.size(); + if (0 != count) { + + std::vector vector{ std::begin(_list), std::end(_list) }; + + id obj; + POPAnimation *anim; + POPAnimationState *state; + + for (auto item : vector) { + obj = item->object; + anim = item->animation; + state = POPAnimationGetState(anim); + + if (nil == obj) { + + // object exists not; stop animating + NSAssert(item->unretainedObject, @"object should exist"); + stopAndCleanup(self, item, true, false); + + } else { + // start if needed + state->startIfNeeded(obj, time, _slowMotionAccumulator); + + // only run active, not paused animations + if (state->active && !state->paused) { + // object exists; animate + applyAnimationTime(obj, state, time); + + FBLogAnimDebug(@"time:%f running:%@", time, item->animation); + + if (state->isDone()) { + // set end value + applyAnimationProgress(obj, state, 1.0); + + // finished succesfully, cleanup + stopAndCleanup(self, item, state->removedOnCompletion, YES); + } + } + } + } + } + + for (id observer in _observers) { + [observer animatorDidAnimate:(id)self]; + } + + updateAnimating(self); + [_delegate animatorDidAnimate:self]; + + [CATransaction commit]; +} + +- (void)addObserver:(id)observer +{ + NSAssert([NSThread isMainThread], @"unexpected thread %@", [NSThread currentThread]); + NSAssert(nil != observer, @"attempting to add nil %@ observer", self); + if (nil == observer) + return; + + NSMutableSet *observers = _observers ? [_observers mutableCopy] : [[NSMutableSet alloc] initWithCapacity:1]; + [observers addObject:observer]; + _observers = observers; + updateAnimating(self); +} + +- (void)removeObserver:(id)observer +{ + NSAssert([NSThread isMainThread], @"unexpected thread %@", [NSThread currentThread]); + NSAssert(nil != observer, @"attempting to remove nil %@ observer", self); + if (nil == observer) + return; + + NSMutableSet *observers = [_observers mutableCopy]; + [observers removeObject:observer]; + _observers = observers; + updateAnimating(self); +} + +@end diff --git a/pop/POPAnimatorPrivate.h b/pop/POPAnimatorPrivate.h new file mode 100644 index 00000000..f04fad68 --- /dev/null +++ b/pop/POPAnimatorPrivate.h @@ -0,0 +1,58 @@ +/** + 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 + +@class POPAnimation; + +@protocol POPAnimatorObserving +@required + +/** + @abstract Called on each observer after animator has advanced. Core Animation actions are disabled by default. + */ +- (void)animatorDidAnimate:(POPAnimator *)animator; + +@end + +@interface POPAnimator () + +#if !TARGET_OS_PHONE +/** + Determines whether or not to use a high priority background thread for animation updates. Using a background thread can result in faster, more responsive updates, but may be less compatible. Defaults to YES. + */ ++ (BOOL)disableBackgroundThread; ++ (void)setDisableBackgroundThread:(BOOL)flag; +#endif + +/** + Exposed for unit testing. + */ +- (void)renderTime:(CFTimeInterval)time; + +/** + Funnel methods for category additions. + */ +- (void)addAnimation:(POPAnimation *)anim forObject:(id)obj key:(NSString *)key; +- (void)removeAllAnimationsForObject:(id)obj; +- (void)removeAnimationForObject:(id)obj key:(NSString *)key; +- (NSArray *)animationKeysForObject:(id)obj; +- (POPAnimation *)animationForObject:(id)obj key:(NSString *)key; + +/** + @abstract Add an animator observer. Observer will be notified of each subsequent animator advance until removal. + */ +- (void)addObserver:(id)observer; + +/** + @abstract Remove an animator observer. + */ +- (void)removeObserver:(id)observer; + +@end diff --git a/pop/POPBasicAnimation.h b/pop/POPBasicAnimation.h new file mode 100644 index 00000000..a993d828 --- /dev/null +++ b/pop/POPBasicAnimation.h @@ -0,0 +1,71 @@ +/** + 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 + +/** + @abstract A concrete basic animation class. + @discussion Animation is achieved through interpolation. + */ +@interface POPBasicAnimation : POPPropertyAnimation + +/** + @abstract The designated initializer. + @returns An instance of a basic animation. + */ ++ (instancetype)animation; + +/** + @abstract Convenience initializer that returns an animation with animatable property of name. + @param name The name of the animatable property. + @returns An instance of a basic animation configured with specified animatable property. + */ ++ (instancetype)animationWithPropertyNamed:(NSString *)name; + +/** + @abstract Convenience constructor. + @returns Returns a basic animation with kCAMediaTimingFunctionDefault timing function. + */ ++ (instancetype)defaultAnimation; + +/** + @abstract Convenience constructor. + @returns Returns a basic animation with kCAMediaTimingFunctionLinear timing function. + */ ++ (instancetype)linearAnimation; + +/** + @abstract Convenience constructor. + @returns Returns a basic animation with kCAMediaTimingFunctionEaseIn timing function. + */ ++ (instancetype)easeInAnimation; + +/** + @abstract Convenience constructor. + @returns Returns a basic animation with kCAMediaTimingFunctionEaseOut timing function. + */ ++ (instancetype)easeOutAnimation; + +/** + @abstract Convenience constructor. + @returns Returns a basic animation with kCAMediaTimingFunctionEaseInEaseOut timing function. + */ ++ (instancetype)easeInEaseOutAnimation; + +/** + @abstract The duration in seconds. Defaults to 0.4. + */ +@property (assign, nonatomic) CFTimeInterval duration; + +/** + @abstract A timing function defining the pacing of the animation. Defaults to nil indicating pacing according to kCAMediaTimingFunctionDefault. + */ +@property (strong, nonatomic) CAMediaTimingFunction *timingFunction; + +@end diff --git a/pop/POPBasicAnimation.mm b/pop/POPBasicAnimation.mm new file mode 100644 index 00000000..f53bba14 --- /dev/null +++ b/pop/POPBasicAnimation.mm @@ -0,0 +1,90 @@ +/** + 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 "POPBasicAnimationInternal.h" + +@implementation POPBasicAnimation + +#undef __state +#define __state ((POPBasicAnimationState *)_state) + +#pragma mark - Lifecycle + ++ (instancetype)animation +{ + return [[self alloc] init]; +} + ++ (instancetype)animationWithPropertyNamed:(NSString *)aName +{ + POPBasicAnimation *anim = [self animation]; + anim.property = [POPAnimatableProperty propertyWithName:aName]; + return anim; +} + +- (void)_initState +{ + _state = new POPBasicAnimationState(self); +} + ++ (instancetype)linearAnimation +{ + POPBasicAnimation *anim = [self animation]; + anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; + return anim; +} + ++ (instancetype)easeInAnimation +{ + POPBasicAnimation *anim = [self animation]; + anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; + return anim; +} + ++ (instancetype)easeOutAnimation +{ + POPBasicAnimation *anim = [self animation]; + anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; + return anim; +} + ++ (instancetype)easeInEaseOutAnimation +{ + POPBasicAnimation *anim = [self animation]; + anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + return anim; +} + ++ (instancetype)defaultAnimation +{ + POPBasicAnimation *anim = [self animation]; + anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; + return anim; +} + +- (id)init +{ + return [self _init]; +} + +#pragma mark - Properties + +DEFINE_RW_PROPERTY(POPBasicAnimationState, duration, setDuration:, CFTimeInterval); +DEFINE_RW_PROPERTY_OBJ(POPBasicAnimationState, timingFunction, setTimingFunction:, CAMediaTimingFunction*, __state->updatedTimingFunction();); + +#pragma mark - Utility + +- (void)_appendDescription:(NSMutableString *)s debug:(BOOL)debug +{ + [super _appendDescription:s debug:debug]; + if (__state->duration) + [s appendFormat:@"; duration = %f", __state->duration]; +} + +@end diff --git a/pop/POPBasicAnimationInternal.h b/pop/POPBasicAnimationInternal.h new file mode 100644 index 00000000..89f9861f --- /dev/null +++ b/pop/POPBasicAnimationInternal.h @@ -0,0 +1,88 @@ +/** + 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 "POPBasicAnimation.h" +#import "POPPropertyAnimationInternal.h" + +// default animation duration +static CGFloat kPOPAnimationDurationDefault = 0.4; + +static void interpolate(POPValueType valueType, NSUInteger count, const CGFloat *fromVec, const CGFloat *toVec, CGFloat *outVec, double p) +{ + switch (valueType) { + case kPOPValueInteger: + case kPOPValueFloat: + case kPOPValuePoint: + case kPOPValueSize: + case kPOPValueRect: + interpolate_vector(count, outVec, fromVec, toVec, p); + break; + default: + NSCAssert(false, @"unhandled type %d", valueType); + break; + } +} + +struct _POPBasicAnimationState : _POPPropertyAnimationState +{ + CAMediaTimingFunction *timingFunction; + double timingControlPoints[4]; + CFTimeInterval duration; + + _POPBasicAnimationState(id __unsafe_unretained anim) : _POPPropertyAnimationState(anim), + duration(kPOPAnimationDurationDefault), + timingFunction(nil) + { + type = kPOPAnimationBasic; + memset(timingControlPoints, 0, sizeof(timingControlPoints)); + } + + bool isDone() { + if (_POPPropertyAnimationState::isDone()) { + return true; + } + return _EQLF_(progress, 1., 1e-2); + } + + void updatedTimingFunction() + { + float vec[4] = {0., 0., 0., 0.}; + [timingFunction getControlPointAtIndex:1 values:&vec[0]]; + [timingFunction getControlPointAtIndex:2 values:&vec[2]]; + for (NSUInteger idx = 0; idx < POP_ARRAY_COUNT(vec); idx++) { + timingControlPoints[idx] = vec[idx]; + } + } + + bool advance(CFTimeInterval time, CFTimeInterval dt, id obj) { + // default timing function + if (!timingFunction) { + ((POPBasicAnimation *)self).timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; + } + + // cap local time to duration + CFTimeInterval t = MIN(time - startTime, duration) / duration; + + // solve for normalized time, aka progresss [0, 1] + double p = timing_function_solve(timingControlPoints, t, SOLVE_EPS(duration)); + + // interpolate and advance + if (p != progress) { + interpolate(valueType, valueCount, fromVec->data(), toVec->data(), currentVec->data(), p); + progress = p; + return true; + } + + clampCurrentValue(); + + return false; + } +}; + +typedef struct _POPBasicAnimationState POPBasicAnimationState; diff --git a/pop/POPCGUtils.h b/pop/POPCGUtils.h new file mode 100644 index 00000000..b268842f --- /dev/null +++ b/pop/POPCGUtils.h @@ -0,0 +1,78 @@ +/** + 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 +#import "POPDefines.h" + +#if TARGET_OS_IPHONE +@class UIColor; +#endif + +POP_EXTERN_C_BEGIN + +NS_INLINE CGPoint values_to_point(const CGFloat values[]) +{ + return CGPointMake(values[0], values[1]); +} + +NS_INLINE CGSize values_to_size(const CGFloat values[]) +{ + return CGSizeMake(values[0], values[1]); +} + +NS_INLINE CGRect values_to_rect(const CGFloat values[]) +{ + return CGRectMake(values[0], values[1], values[2], values[3]); +} + +NS_INLINE void values_from_point(CGFloat values[], CGPoint p) +{ + values[0] = p.x; + values[1] = p.y; +} + +NS_INLINE void values_from_size(CGFloat values[], CGSize s) +{ + values[0] = s.width; + values[1] = s.height; +} + +NS_INLINE void values_from_rect(CGFloat values[], CGRect r) +{ + values[0] = r.origin.x; + values[1] = r.origin.y; + values[2] = r.size.width; + values[3] = r.size.height; +} + +/** + Takes a CGColorRef and converts it into RGBA components, if necessary. + */ +extern void POPCGColorGetRGBAComponents(CGColorRef color, CGFloat components[]); + +/** + Takes RGBA components and returns a CGColorRef. + */ +extern CGColorRef POPCGColorRGBACreate(const CGFloat components[]) CF_RETURNS_RETAINED; + +#if TARGET_OS_IPHONE + +/** + Takes a UIColor and converts it into RGBA components, if necessary. + */ +extern void POPUIColorGetRGBAComponents(UIColor *color, CGFloat components[]); + +/** + Takes RGBA components and returns a UIColor. + */ +extern UIColor *POPUIColorRGBACreate(const CGFloat components[]) NS_RETURNS_RETAINED; + +#endif + +POP_EXTERN_C_END diff --git a/pop/POPCGUtils.mm b/pop/POPCGUtils.mm new file mode 100644 index 00000000..97e68327 --- /dev/null +++ b/pop/POPCGUtils.mm @@ -0,0 +1,75 @@ +/** + 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 "POPCGUtils.h" + +#if TARGET_OS_IPHONE +#import +#else +#import +#endif + +void POPCGColorGetRGBAComponents(CGColorRef color, CGFloat components[]) +{ + if (!color) { +#if TARGET_OS_IPHONE + color = [UIColor clearColor].CGColor; +#else + color = [NSColor clearColor].CGColor; +#endif + } + + const CGFloat *colors = CGColorGetComponents(color); + size_t count = CGColorGetNumberOfComponents(color); + + if (4 == count) { + // RGB colorspace + components[0] = colors[0]; + components[1] = colors[1]; + components[2] = colors[2]; + components[3] = colors[3]; + } else if (2 == count) { + // Grey colorspace + components[0] = components[1] = components[2] = colors[0]; + components[3] = colors[1]; + } else { + // TODO HSV and CMYK conversion + NSCAssert(NO, @"unsuported color space conversion, component count:%lu", count); + } +} + +CGColorRef POPCGColorRGBACreate(const CGFloat components[]) +{ +#if TARGET_OS_IPHONE + CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); + CGColorRef color = CGColorCreate(space, components); + CGColorSpaceRelease(space); + return color; +#else + return CGColorCreateGenericRGB(components[0], components[1], components[2], components[3]); +#endif +} + +#if TARGET_OS_IPHONE + +void POPUIColorGetRGBAComponents(UIColor *color, CGFloat components[]) +{ + return POPCGColorGetRGBAComponents(color.CGColor, components); +} + +UIColor *POPUIColorRGBACreate(const CGFloat components[]) +{ + CGColorRef colorRef = POPCGColorRGBACreate(components); + UIColor *color = [[UIColor alloc] initWithCGColor:colorRef]; + CGColorRelease(colorRef); + return color; +} + +#endif + diff --git a/pop/POPCustomAnimation.h b/pop/POPCustomAnimation.h new file mode 100644 index 00000000..fd93da68 --- /dev/null +++ b/pop/POPCustomAnimation.h @@ -0,0 +1,46 @@ +/** + 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 + +@class POPCustomAnimation; + +/** + @abstract POPCustomAnimationBlock is the callback block of a custom animation. + @discussion This block will be executed for each animation frame and should update the property or properties being animated based on current timing. + @param target The object being animated. Reference the passed in target to help avoid retain loops. + @param target The custom animation instance. Use to determine the current and elapsed time since last callback. Reference the passed in animation to help avoid retain loops. + @return Flag indicating whether the animation should continue animating. Return NO to indicate animation is done. + */ +typedef BOOL (^POPCustomAnimationBlock)(id target, POPCustomAnimation *animation); + +/** + @abstract POPCustomAnimation is a concrete animation subclass for custom animations. + */ +@interface POPCustomAnimation : POPAnimation + +/** +@abstract Creates and returns an initialized custom animation instance. +@discussion This is the designated initializer. +@param block The custom animation callback block. See {@ref POPCustomAnimationBlock}. +@return The initialized custom animation instance. +*/ ++ (instancetype)animationWithBlock:(POPCustomAnimationBlock)block; + +/** + @abstract The current animation time at time of callback. + */ +@property (readonly, nonatomic) CFTimeInterval currentTime; + +/** + @abstract The elapsed animation time since last callback. + */ +@property (readonly, nonatomic) CFTimeInterval elapsedTime; + +@end diff --git a/pop/POPCustomAnimation.mm b/pop/POPCustomAnimation.mm new file mode 100644 index 00000000..beb309cd --- /dev/null +++ b/pop/POPCustomAnimation.mm @@ -0,0 +1,53 @@ +/** + 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 "POPCustomAnimation.h" +#import "POPAnimationInternal.h" + +@interface POPCustomAnimation () +@property (nonatomic, copy) POPCustomAnimationBlock animate; +@end + +@implementation POPCustomAnimation + ++ (instancetype)animationWithBlock:(BOOL(^)(id target, POPCustomAnimation *))block +{ + POPCustomAnimation *b = [[self alloc] _init]; + b.animate = block; + return b; +} + +- (id)_init +{ + self = [super _init]; + if (nil != self) { + _state->type = kPOPAnimationCustom; + } + return self; +} + +- (CFTimeInterval)beginTime +{ + POPAnimationState *s = POPAnimationGetState(self); + return s->startTime > 0 ? s->startTime : s->beginTime; +} + +- (BOOL)_advance:(id)object currentTime:(CFTimeInterval)currentTime elapsedTime:(CFTimeInterval)elapsedTime +{ + _currentTime = currentTime; + _elapsedTime = elapsedTime; + return _animate(object, self); +} + +- (void)_appendDescription:(NSMutableString *)s debug:(BOOL)debug +{ + [s appendFormat:@"; elapsedTime = %f; currentTime = %f;", _elapsedTime, _currentTime]; +} + +@end diff --git a/pop/POPDecayAnimation.h b/pop/POPDecayAnimation.h new file mode 100644 index 00000000..9018cd83 --- /dev/null +++ b/pop/POPDecayAnimation.h @@ -0,0 +1,54 @@ +/** + 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 + +/** + @abstract A concrete decay animation class. + @discussion Animation is achieved through gradual decay of animation value. + */ +@interface POPDecayAnimation : POPPropertyAnimation + +/** + @abstract The designated initializer. + @returns An instance of a decay animation. + */ ++ (instancetype)animation; + +/** + @abstract Convenience initializer that returns an animation with animatable property of name. + @param name The name of the animatable property. + @returns An instance of a decay animation configured with specified animatable property. + */ ++ (instancetype)animationWithPropertyNamed:(NSString *)name; + +/** + @abstract The current velocity value. + @discussion Set before animation start to account for initial velocity. Expressed in change of value units per second. + */ +@property (copy, nonatomic) id velocity; + +/** + @abstract The deceleration factor. + @discussion Values specifies should be in the range [0, 1]. Lower values results in faster deceleration. Defaults to 0.998. + */ +@property (assign, nonatomic) CGFloat deceleration; + +/** + @abstract The expected duration. + @discussion Derived based on input velocity and deceleration values. + */ +@property (readonly, assign, nonatomic) CFTimeInterval duration; + +/** + The to value is derived based on input velocity and deceleration. + */ +- (void)setToValue:(id)toValue NS_UNAVAILABLE; + +@end diff --git a/pop/POPDecayAnimation.mm b/pop/POPDecayAnimation.mm new file mode 100644 index 00000000..2a0bbc75 --- /dev/null +++ b/pop/POPDecayAnimation.mm @@ -0,0 +1,123 @@ +/** + 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 "POPDecayAnimationInternal.h" + +@implementation POPDecayAnimation + +#pragma mark - Lifecycle + +#undef __state +#define __state ((POPDecayAnimationState *)_state) + ++ (instancetype)animation +{ + return [[self alloc] init]; +} + ++ (instancetype)animationWithPropertyNamed:(NSString *)aName +{ + POPDecayAnimation *anim = [self animation]; + anim.property = [POPAnimatableProperty propertyWithName:aName]; + return anim; +} + +- (id)init +{ + return [self _init]; +} + +- (void)_initState +{ + _state = new POPDecayAnimationState(self); +} + +#pragma mark - Properties + +DEFINE_RW_PROPERTY(POPDecayAnimationState, deceleration, setDeceleration:, CGFloat, __state->toVec = NULL;); + +@dynamic velocity; + +- (id)toValue +{ + [self _ensureComputedProperties]; + return POPBox(__state->toVec, __state->valueType); +} + +- (CFTimeInterval)duration +{ + [self _ensureComputedProperties]; + return __state->duration; +} + +- (void)setFromValue:(id)fromValue +{ + super.fromValue = fromValue; + [self _invalidateComputedProperties]; +} + +- (void)setToValue:(id)aValue +{ + // no-op + NSLog(@"ignoring to value on decay animation %@", self); +} + +- (id)velocity +{ + return POPBox(__state->velocityVec, __state->valueType); +} + +- (void)setVelocity:(id)aValue +{ + VectorRef vec = POPUnbox(aValue, __state->valueType, __state->valueCount, YES); + + if (!vec_equal(vec, __state->velocityVec)) { + __state->velocityVec = vec; + + if (__state->tracing) { + [__state->tracer updateVelocity:aValue]; + } + + [self _invalidateComputedProperties]; + + // automatically unpause active animations + if (__state->active && __state->paused) { + __state->fromVec = NULL; + __state->setPaused(false); + } + } +} + +#pragma mark - Utility + +- (void)_ensureComputedProperties +{ + if (NULL == __state->toVec) { + __state->computeDestinationValues(); + } +} + +- (void)_invalidateComputedProperties +{ + __state->toVec = NULL; + __state->duration = 0; +} + +- (void)_appendDescription:(NSMutableString *)s debug:(BOOL)debug +{ + [super _appendDescription:s debug:debug]; + + if (NULL != __state->toVec) + [s appendFormat:@"; duration = %f", self.duration]; + + if (__state->deceleration) + [s appendFormat:@"; deceleration = %f", __state->deceleration]; +} + +@end diff --git a/pop/POPDecayAnimationInternal.h b/pop/POPDecayAnimationInternal.h new file mode 100644 index 00000000..aca98c43 --- /dev/null +++ b/pop/POPDecayAnimationInternal.h @@ -0,0 +1,117 @@ +/** + 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 "POPDecayAnimation.h" +#import "POPPropertyAnimationInternal.h" + +// minimal velocity factor before decay animation is considered complete, in units / s +static CGFloat kPOPAnimationDecayMinimalVelocityFactor = 5.; + +// default decay animation deceleration +static CGFloat kPOPAnimationDecayDecelerationDefault = 0.998; + +static void decay_position(CGFloat *x, CGFloat *v, NSUInteger count, CFTimeInterval dt, CGFloat deceleration) +{ + dt *= 1000; + + // v0 = v / 1000 + // v = v0 * powf(deceleration, dt); + // v = v * 1000; + + // x0 = x; + // x = x0 + v0 * deceleration * (1 - powf(deceleration, dt)) / (1 - deceleration) + float v0[count]; + float kv = powf(deceleration, dt); + float kx = deceleration * (1 - kv) / (1 - deceleration); + + for (NSUInteger idx = 0; idx < count; idx++) { + v0[idx] = v[idx] / 1000.; + v[idx] = v0[idx] * kv * 1000.; + x[idx] = x[idx] + v0[idx] * kx; + } +} + +struct _POPDecayAnimationState : _POPPropertyAnimationState +{ + double deceleration; + CFTimeInterval duration; + + _POPDecayAnimationState(id __unsafe_unretained anim) : + _POPPropertyAnimationState(anim), + duration(0), + deceleration(kPOPAnimationDecayDecelerationDefault) { + type = kPOPAnimationDecay; + } + + bool isDone() { + if (_POPPropertyAnimationState::isDone()) { + return true; + } + + CGFloat f = dynamicsThreshold * kPOPAnimationDecayMinimalVelocityFactor; + const CGFloat *velocityValues = vec_data(velocityVec); + for (NSUInteger idx = 0; idx < valueCount; idx++) { + if (fabsf(velocityValues[idx]) >= f) + return false; + } + return true; + + } + + void computeDestinationValues() { + // to value assuming final velocity as a factor of dynamics threshold + // derived from v' = v * d^dt used in decay_position + // to compute the to value with maximal dt, p' = p + (v * d) / (1 - d) + VectorRef fromValue = NULL != currentVec ? currentVec : fromVec; + if (!fromValue) { + return; + } + + VectorRef toValue(Vector::new_vector(fromValue.get())); + + // compute duration till threshold velocity + Vector4r scaledVelocity = vector4(velocityVec) / 1000.; + + double k = dynamicsThreshold * kPOPAnimationDecayMinimalVelocityFactor / 1000.; + double vx = k / scaledVelocity.x; + double vy = k / scaledVelocity.y; + double vz = k / scaledVelocity.z; + double vw = k / scaledVelocity.w; + double d = log(deceleration) * 1000.; + duration = MAX(MAX(MAX(log(fabs(vx)) / d, log(fabs(vy)) / d), log(fabs(vz)) / d), log(fabs(vw)) / d); + + // ensure velocity threshold is exceeded + if (isnan(duration) || duration < 0) { + duration = 0; + } else { + // compute to value + Vector4r velocity = velocityVec->vector4r(); + decay_position(toValue->data(), velocity.data(), valueCount, duration, deceleration); + } + + toVec = toValue; + } + + bool advance(CFTimeInterval time, CFTimeInterval dt, id obj) { + // advance past not yet initialized animations + if (NULL == currentVec) { + return false; + } + + decay_position(currentVec->data(), velocityVec->data(), valueCount, dt, deceleration); + + // clamp to compute end value; avoid possibility of decaying past + clampCurrentValue(kPOPAnimationClampEnd | clampMode); + + return true; + } + +}; + +typedef struct _POPDecayAnimationState POPDecayAnimationState; diff --git a/pop/POPDefines.h b/pop/POPDefines.h new file mode 100644 index 00000000..92cf8018 --- /dev/null +++ b/pop/POPDefines.h @@ -0,0 +1,29 @@ +/** + 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. + */ + +#ifndef POP_POPDefines_h +#define POP_POPDefines_h + +#ifdef __cplusplus +# define POP_EXTERN_C_BEGIN extern "C" { +# define POP_EXTERN_C_END } +#else +# define POP_EXTERN_C_BEGIN +# define POP_EXTERN_C_END +#endif + +#define POP_ARRAY_COUNT(x) sizeof(x) / sizeof(x[0]) + +#if defined (__cplusplus) && defined (__GNUC__) +# define POP_NOTHROW __attribute__ ((nothrow)) +#else +# define POP_NOTHROW +#endif + +#endif diff --git a/pop/POPGeometry.h b/pop/POPGeometry.h new file mode 100644 index 00000000..cc783175 --- /dev/null +++ b/pop/POPGeometry.h @@ -0,0 +1,53 @@ +/** + 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 + +#if TARGET_OS_IPHONE +#import +#endif + +#if !TARGET_OS_IPHONE + +/** NSValue extensions to support animatable types. */ +@interface NSValue (POP) + +/** + @abstract Creates an NSValue given a CGPoint. + */ ++ (NSValue *)valueWithCGPoint:(CGPoint)point; + +/** + @abstract Creates an NSValue given a CGSize. + */ ++ (NSValue *)valueWithCGSize:(CGSize)size; + +/** + @abstract Creates an NSValue given a CGRect. + */ ++ (NSValue *)valueWithCGRect:(CGRect)rect; + +/** + @abstract Returns the underlying CGPoint value. + */ +- (CGPoint)CGPointValue; + +/** + @abstract Returns the underlying CGSize value. + */ +- (CGSize)CGSizeValue; + +/** + @abstract Returns the underlying CGRect value. + */ +- (CGRect)CGRectValue; + +@end + +#endif diff --git a/pop/POPGeometry.mm b/pop/POPGeometry.mm new file mode 100644 index 00000000..29c75a44 --- /dev/null +++ b/pop/POPGeometry.mm @@ -0,0 +1,67 @@ +/** + 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 "POPGeometry.h" + +#if !TARGET_OS_IPHONE +@implementation NSValue (POP) + ++ (NSValue *)valueWithCGPoint:(CGPoint)point { + return [NSValue valueWithBytes:&point objCType:@encode(CGPoint)]; +} + ++ (NSValue *)valueWithCGSize:(CGSize)size { + return [NSValue valueWithBytes:&size objCType:@encode(CGSize)]; +} + ++ (NSValue *)valueWithCGRect:(CGRect)rect { + return [NSValue valueWithBytes:&rect objCType:@encode(CGRect)]; +} + ++ (NSValue *)valueWithCFRange:(CFRange)range { + return [NSValue valueWithBytes:&range objCType:@encode(CFRange)]; +} + ++ (NSValue *)valueWithCGAffineTransform:(CGAffineTransform)transform +{ + return [NSValue valueWithBytes:&transform objCType:@encode(CGAffineTransform)]; +} + +- (CGPoint)CGPointValue { + CGPoint result; + [self getValue:&result]; + return result; +} + +- (CGSize)CGSizeValue { + CGSize result; + [self getValue:&result]; + return result; +} + +- (CGRect)CGRectValue { + CGRect result; + [self getValue:&result]; + return result; +} + +- (CFRange)CFRangeValue { + CFRange result; + [self getValue:&result]; + return result; +} + +- (CGAffineTransform)CGAffineTransformValue { + CGAffineTransform result; + [self getValue:&result]; + return result; +} +@end + +#endif diff --git a/pop/POPLayerExtras.h b/pop/POPLayerExtras.h new file mode 100644 index 00000000..25a28750 --- /dev/null +++ b/pop/POPLayerExtras.h @@ -0,0 +1,196 @@ +/** + 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 + +#import + +POP_EXTERN_C_BEGIN + +#pragma mark - Scale + +/** + @abstract Returns layer scale factor for the x axis. + */ +extern CGFloat POPLayerGetScaleX(CALayer *l); + +/** + @abstract Set layer scale factor for the x axis. + */ +extern void POPLayerSetScaleX(CALayer *l, CGFloat f); + +/** + @abstract Returns layer scale factor for the y axis. + */ +extern CGFloat POPLayerGetScaleY(CALayer *l); + +/** + @abstract Set layer scale factor for the y axis. + */ +extern void POPLayerSetScaleY(CALayer *l, CGFloat f); + +/** + @abstract Returns layer scale factor for the z axis. + */ +extern CGFloat POPLayerGetScaleZ(CALayer *l); + +/** + @abstract Set layer scale factor for the z axis. + */ +extern void POPLayerSetScaleZ(CALayer *l, CGFloat f); + +/** + @abstract Returns layer scale factors for x and y access as point. + */ +extern CGPoint POPLayerGetScaleXY(CALayer *l); + +/** + @abstract Sets layer x and y scale factors given point. + */ +extern void POPLayerSetScaleXY(CALayer *l, CGPoint p); + +#pragma mark - Translation + +/** + @abstract Returns layer translation factor for the x axis. + */ +extern CGFloat POPLayerGetTranslationX(CALayer *l); + +/** + @abstract Set layer translation factor for the x axis. + */ +extern void POPLayerSetTranslationX(CALayer *l, CGFloat f); + +/** + @abstract Returns layer translation factor for the y axis. + */ +extern CGFloat POPLayerGetTranslationY(CALayer *l); + +/** + @abstract Set layer translation factor for the y axis. + */ +extern void POPLayerSetTranslationY(CALayer *l, CGFloat f); + +/** + @abstract Returns layer translation factor for the z axis. + */ +extern CGFloat POPLayerGetTranslationZ(CALayer *l); + +/** + @abstract Set layer translation factor for the z axis. + */ +extern void POPLayerSetTranslationZ(CALayer *l, CGFloat f); + +/** + @abstract Returns layer translation factors for x and y access as point. + */ +extern CGPoint POPLayerGetTranslationXY(CALayer *l); + +/** + @abstract Sets layer x and y translation factors given point. + */ +extern void POPLayerSetTranslationXY(CALayer *l, CGPoint p); + +#pragma mark - Rotation + +/** + @abstract Returns layer rotation, in radians, in the X axis. + */ +extern CGFloat POPLayerGetRotationX(CALayer *l); + +/** + @abstract Sets layer rotation, in radians, in the X axis. + */ +extern void POPLayerSetRotationX(CALayer *l, CGFloat f); + +/** + @abstract Returns layer rotation, in radians, in the Y axis. + */ +extern CGFloat POPLayerGetRotationY(CALayer *l); + +/** + @abstract Sets layer rotation, in radians, in the Y axis. + */ +extern void POPLayerSetRotationY(CALayer *l, CGFloat f); + +/** + @abstract Returns layer rotation, in radians, in the Z axis. + */ +extern CGFloat POPLayerGetRotationZ(CALayer *l); + +/** + @abstract Sets layer rotation, in radians, in the Z axis. + */ +extern void POPLayerSetRotationZ(CALayer *l, CGFloat f); + +/** + @abstract Returns layer rotation, in radians, in the Z axis. + */ +extern CGFloat POPLayerGetRotation(CALayer *l); + +/** + @abstract Sets layer rotation, in radians, in the Z axis. + */ +extern void POPLayerSetRotation(CALayer *l, CGFloat f); + +#pragma mark - Sublayer Scale + +/** + @abstract Returns sublayer scale factors for x and y access as point. + */ +extern CGPoint POPLayerGetSubScaleXY(CALayer *l); + +/** + @abstract Sets sublayer x and y scale factors given point. + */ +extern void POPLayerSetSubScaleXY(CALayer *l, CGPoint p); + +#pragma mark - Sublayer Translation + +/** + @abstract Returns sublayer translation factor for the x axis. + */ +extern CGFloat POPLayerGetSubTranslationX(CALayer *l); + +/** + @abstract Set sublayer translation factor for the x axis. + */ +extern void POPLayerSetSubTranslationX(CALayer *l, CGFloat f); + +/** + @abstract Returns sublayer translation factor for the y axis. + */ +extern CGFloat POPLayerGetSubTranslationY(CALayer *l); + +/** + @abstract Set sublayer translation factor for the y axis. + */ +extern void POPLayerSetSubTranslationY(CALayer *l, CGFloat f); + +/** + @abstract Returns sublayer translation factor for the z axis. + */ +extern CGFloat POPLayerGetSubTranslationZ(CALayer *l); + +/** + @abstract Set sublayer translation factor for the z axis. + */ +extern void POPLayerSetSubTranslationZ(CALayer *l, CGFloat f); + +/** + @abstract Returns sublayer translation factors for x and y access as point. + */ +extern CGPoint POPLayerGetSubTranslationXY(CALayer *l); + +/** + @abstract Sets sublayer x and y translation factors given point. + */ +extern void POPLayerSetSubTranslationXY(CALayer *l, CGPoint p); + +POP_EXTERN_C_END diff --git a/pop/POPLayerExtras.mm b/pop/POPLayerExtras.mm new file mode 100644 index 00000000..aff8af25 --- /dev/null +++ b/pop/POPLayerExtras.mm @@ -0,0 +1,268 @@ +/** + 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 "POPLayerExtras.h" + +#include "TransformationMatrix.h" + +using namespace WebCore; + +#define DECOMPOSE_TRANSFORM(L) \ + TransformationMatrix _m(L.transform); \ + TransformationMatrix::DecomposedType _d; \ + _m.decompose(_d); + +#define RECOMPOSE_TRANSFORM(L) \ + _m.recompose(_d); \ + L.transform = _m.transform3d(); + +#define RECOMPOSE_ROT_TRANSFORM(L) \ + _m.recompose(_d, true); \ + L.transform = _m.transform3d(); + +#define DECOMPOSE_SUBLAYER_TRANSFORM(L) \ + TransformationMatrix _m(L.sublayerTransform); \ + TransformationMatrix::DecomposedType _d; \ + _m.decompose(_d); + +#define RECOMPOSE_SUBLAYER_TRANSFORM(L) \ + _m.recompose(_d); \ + L.sublayerTransform = _m.transform3d(); + +#pragma mark - Scale + +CGFloat POPLayerGetScaleX(CALayer *l) +{ + DECOMPOSE_TRANSFORM(l); + return _d.scaleX; +} + +void POPLayerSetScaleX(CALayer *l, CGFloat f) +{ + DECOMPOSE_TRANSFORM(l); + _d.scaleX = f; + RECOMPOSE_TRANSFORM(l); +} + +CGFloat POPLayerGetScaleY(CALayer *l) +{ + DECOMPOSE_TRANSFORM(l); + return _d.scaleY; +} + +void POPLayerSetScaleY(CALayer *l, CGFloat f) +{ + DECOMPOSE_TRANSFORM(l); + _d.scaleY = f; + RECOMPOSE_TRANSFORM(l); +} + +CGFloat POPLayerGetScaleZ(CALayer *l) +{ + DECOMPOSE_TRANSFORM(l); + return _d.scaleZ; +} + +void POPLayerSetScaleZ(CALayer *l, CGFloat f) +{ + DECOMPOSE_TRANSFORM(l); + _d.scaleZ = f; + RECOMPOSE_TRANSFORM(l); +} + +CGPoint POPLayerGetScaleXY(CALayer *l) +{ + DECOMPOSE_TRANSFORM(l); + return CGPointMake(_d.scaleX, _d.scaleY); +} + +void POPLayerSetScaleXY(CALayer *l, CGPoint p) +{ + DECOMPOSE_TRANSFORM(l); + _d.scaleX = p.x; + _d.scaleY = p.y; + RECOMPOSE_TRANSFORM(l); +} + +#pragma mark - Translation + +CGFloat POPLayerGetTranslationX(CALayer *l) +{ + DECOMPOSE_TRANSFORM(l); + return _d.translateX; +} + +void POPLayerSetTranslationX(CALayer *l, CGFloat f) +{ + DECOMPOSE_TRANSFORM(l); + _d.translateX = f; + RECOMPOSE_TRANSFORM(l); +} + +CGFloat POPLayerGetTranslationY(CALayer *l) +{ + DECOMPOSE_TRANSFORM(l); + return _d.translateY; +} + +void POPLayerSetTranslationY(CALayer *l, CGFloat f) +{ + DECOMPOSE_TRANSFORM(l); + _d.translateY = f; + RECOMPOSE_TRANSFORM(l); +} + +CGFloat POPLayerGetTranslationZ(CALayer *l) +{ + DECOMPOSE_TRANSFORM(l); + return _d.translateZ; +} + +void POPLayerSetTranslationZ(CALayer *l, CGFloat f) +{ + DECOMPOSE_TRANSFORM(l); + _d.translateZ = f; + RECOMPOSE_TRANSFORM(l); +} + +CGPoint POPLayerGetTranslationXY(CALayer *l) +{ + DECOMPOSE_TRANSFORM(l); + return CGPointMake(_d.translateX, _d.translateY); +} + +void POPLayerSetTranslationXY(CALayer *l, CGPoint p) +{ + DECOMPOSE_TRANSFORM(l); + _d.translateX = p.x; + _d.translateY = p.y; + RECOMPOSE_TRANSFORM(l); +} + +#pragma mark - Rotation + +CGFloat POPLayerGetRotationX(CALayer *l) +{ + DECOMPOSE_TRANSFORM(l); + return _d.rotateX; +} + +void POPLayerSetRotationX(CALayer *l, CGFloat f) +{ + DECOMPOSE_TRANSFORM(l); + _d.rotateX = f; + RECOMPOSE_ROT_TRANSFORM(l); +} + +CGFloat POPLayerGetRotationY(CALayer *l) +{ + DECOMPOSE_TRANSFORM(l); + return _d.rotateY; +} + +void POPLayerSetRotationY(CALayer *l, CGFloat f) +{ + DECOMPOSE_TRANSFORM(l); + _d.rotateY = f; + RECOMPOSE_ROT_TRANSFORM(l); +} + +CGFloat POPLayerGetRotationZ(CALayer *l) +{ + DECOMPOSE_TRANSFORM(l); + return _d.rotateZ; +} + +void POPLayerSetRotationZ(CALayer *l, CGFloat f) +{ + DECOMPOSE_TRANSFORM(l); + _d.rotateZ = f; + RECOMPOSE_ROT_TRANSFORM(l); +} + +CGFloat POPLayerGetRotation(CALayer *l) +{ + return POPLayerGetRotationZ(l); +} + +void POPLayerSetRotation(CALayer *l, CGFloat f) +{ + POPLayerSetRotationZ(l, f); +} + +#pragma mark - Sublayer Scale + +CGPoint POPLayerGetSubScaleXY(CALayer *l) +{ + DECOMPOSE_SUBLAYER_TRANSFORM(l); + return CGPointMake(_d.scaleX, _d.scaleY); +} + +void POPLayerSetSubScaleXY(CALayer *l, CGPoint p) +{ + DECOMPOSE_SUBLAYER_TRANSFORM(l); + _d.scaleX = p.x; + _d.scaleY = p.y; + RECOMPOSE_SUBLAYER_TRANSFORM(l); +} + +#pragma mark - Sublayer Translation + +extern CGFloat POPLayerGetSubTranslationX(CALayer *l) +{ + DECOMPOSE_SUBLAYER_TRANSFORM(l); + return _d.translateX; +} + +extern void POPLayerSetSubTranslationX(CALayer *l, CGFloat f) +{ + DECOMPOSE_SUBLAYER_TRANSFORM(l); + _d.translateX = f; + RECOMPOSE_SUBLAYER_TRANSFORM(l); +} + +extern CGFloat POPLayerGetSubTranslationY(CALayer *l) +{ + DECOMPOSE_SUBLAYER_TRANSFORM(l); + return _d.translateY; +} + +extern void POPLayerSetSubTranslationY(CALayer *l, CGFloat f) +{ + DECOMPOSE_SUBLAYER_TRANSFORM(l); + _d.translateY = f; + RECOMPOSE_SUBLAYER_TRANSFORM(l); +} + +extern CGFloat POPLayerGetSubTranslationZ(CALayer *l) +{ + DECOMPOSE_SUBLAYER_TRANSFORM(l); + return _d.translateZ; +} + +extern void POPLayerSetSubTranslationZ(CALayer *l, CGFloat f) +{ + DECOMPOSE_SUBLAYER_TRANSFORM(l); + _d.translateZ = f; + RECOMPOSE_SUBLAYER_TRANSFORM(l); +} + +extern CGPoint POPLayerGetSubTranslationXY(CALayer *l) +{ + DECOMPOSE_SUBLAYER_TRANSFORM(l); + return CGPointMake(_d.translateX, _d.translateY); +} + +extern void POPLayerSetSubTranslationXY(CALayer *l, CGPoint p) +{ + DECOMPOSE_SUBLAYER_TRANSFORM(l); + _d.translateX = p.x; + _d.translateY = p.y; + RECOMPOSE_SUBLAYER_TRANSFORM(l); +} diff --git a/pop/POPMath.h b/pop/POPMath.h new file mode 100644 index 00000000..a5142f94 --- /dev/null +++ b/pop/POPMath.h @@ -0,0 +1,56 @@ +/** + 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 +#import + +#import + +#import "POPVector.h" + +NS_INLINE CGFloat sqrtr(CGFloat f) +{ +#if CGFLOAT_IS_DOUBLE + return sqrt(f); +#else + return sqrtf(f); +#endif +} + +// round to nearest sub; pass 2.0 to round to every 0.5 (eg: retina pixels) +NS_INLINE CGFloat POPSubRound(CGFloat f, CGFloat sub) +{ + return round(f * sub) / sub; +} + +#define MIX(a, b, f) ((a) + (f) * ((b) - (a))) + +// the longer the duration, the higher the necessary precision +#define SOLVE_EPS(dur) (1. / (1000. * (dur))) + +#define _EQLF_(x, y, epsilon) (fabsf ((x) - (y)) < epsilon) + +extern void interpolate_vector(NSUInteger count, CGFloat *dst, const CGFloat *from, const CGFloat *to, double f); + +extern double timing_function_solve(const double vec[4], double t, double eps); + +// quadratic mapping of t [0, 1] to [start, end] +extern double quadratic_out_interpolation(double t, double start, double end); + +// normalize value to [0, 1] based on its range [startValue, endValue] +extern double normalize(double value, double startValue, double endValue); + +// project a normalized value [0, 1] to a given range [start, end] +extern double project_normal(double n, double start, double end); + +// solve a quadratic equation of the form a * x^2 + b * x + c = 0 +extern void quadratic_solve(CGFloat a, CGFloat b, CGFloat c, CGFloat &x1, CGFloat &x2); + +// for a given tension return the bouncy 3 friction that produces no bounce +extern double b3_nobounce(double tension); diff --git a/pop/POPMath.mm b/pop/POPMath.mm new file mode 100644 index 00000000..dc494573 --- /dev/null +++ b/pop/POPMath.mm @@ -0,0 +1,82 @@ +/** + 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 "POPMath.h" +#import "UnitBezier.h" +#import "POPAnimationPrivate.h" + +void interpolate_vector(NSUInteger count, CGFloat *dst, const CGFloat *from, const CGFloat *to, double f) +{ + for (NSUInteger idx = 0; idx < count; idx++) { + dst[idx] = MIX(from[idx], to[idx], f); + } +} + +double timing_function_solve(const double vec[4], double t, double eps) +{ + WebCore::UnitBezier bezier(vec[0], vec[1], vec[2], vec[3]); + return bezier.solve(t, eps); +} + +double normalize(double value, double startValue, double endValue) +{ + return (value - startValue) / (endValue - startValue); +} + +double project_normal(double n, double start, double end) +{ + return start + (n * (end - start)); +} + +static double linear_interpolation(double t, double start, double end) +{ + return t * end + (1.f - t) * start; +} + +double quadratic_out_interpolation(double t, double start, double end) +{ + return linear_interpolation(2*t - t*t, start, end); +} + +static double b3_friction1(double x) +{ + return (0.0007 * pow(x, 3)) - (0.031 * pow(x, 2)) + 0.64 * x + 1.28; +} + +static double b3_friction2(double x) +{ + return (0.000044 * pow(x, 3)) - (0.006 * pow(x, 2)) + 0.36 * x + 2.; +} + +static double b3_friction3(double x) +{ + return (0.00000045 * pow(x, 3)) - (0.000332 * pow(x, 2)) + 0.1078 * x + 5.84; +} + +double b3_nobounce(double tension) +{ + double friction = 0; + if (tension <= 18.) { + friction = b3_friction1(tension); + } else if (tension > 18 && tension <= 44) { + friction = b3_friction2(tension); + } else if (tension > 44) { + friction = b3_friction3(tension); + } else { + assert(false); + } + return friction; +} + +void quadratic_solve(CGFloat a, CGFloat b, CGFloat c, CGFloat &x1, CGFloat &x2) +{ + CGFloat discriminant = sqrt(b * b - 4 * a * c); + x1 = (-b + discriminant) / (2 * a); + x2 = (-b - discriminant) / (2 * a); +} diff --git a/pop/POPPropertyAnimation.h b/pop/POPPropertyAnimation.h new file mode 100644 index 00000000..9dcfe2a1 --- /dev/null +++ b/pop/POPPropertyAnimation.h @@ -0,0 +1,65 @@ +/** + 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 +#import + +/** + @abstract Flags for clamping animation values. + @discussion Animation values can optionally be clamped to avoid overshoot. kPOPAnimationClampStart ensures values are more than fromValue and kPOPAnimationClampEnd ensures values are less than toValue. + */ +typedef NS_OPTIONS(NSUInteger, POPAnimationClampFlags) +{ + kPOPAnimationClampNone = 0, + kPOPAnimationClampStart = 1UL << 0, + kPOPAnimationClampEnd = 1UL << 1, + kPOPAnimationClampBoth = kPOPAnimationClampStart | kPOPAnimationClampEnd, +}; + +/** + @abstract The semi-concrete property animation subclass. + */ +@interface POPPropertyAnimation : POPAnimation + +/** + @abstract The property to animate. + */ +@property (strong, nonatomic) POPAnimatableProperty *property; + +/** + @abstract The value to animate from. + @discussion The value type should match the property. If unspecified, the value is initialized to the object's current value on animation start. + */ +@property (copy, nonatomic) id fromValue; + +/** + @abstract The value to animate to. + @discussion The value type should match the property. If unspecified, the value is initialized to the object's current value on animation start. + */ +@property (copy, nonatomic) id toValue; + +/** + @abstract The rounding factor applied to the current animated value. + @discussion Specify 1.0 to animate between integral values. Defaults to 0 meaning no rounding. + */ +@property (assign, nonatomic) CGFloat roundingFactor; + +/** + @abstract The clamp mode applied to the current animated value. + @discussion See {@ref POPAnimationClampFlags} for possible values. Defaults to kPOPAnimationClampNone. + */ +@property (assign, nonatomic) NSUInteger clampMode; + +/** + @abstract The flag indicating whether values should be "added" each frame, rather than set. + @discussion Addition may be type dependent. Defaults to NO. + */ +@property (assign, nonatomic, getter = isAdditive) BOOL additive; + +@end diff --git a/pop/POPPropertyAnimation.mm b/pop/POPPropertyAnimation.mm new file mode 100644 index 00000000..ccabc488 --- /dev/null +++ b/pop/POPPropertyAnimation.mm @@ -0,0 +1,105 @@ +/** + 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 "POPPropertyAnimationInternal.h" + +@implementation POPPropertyAnimation + +#pragma mark - Lifecycle + +#undef __state +#define __state ((POPPropertyAnimationState *)_state) + +- (void)_initState +{ + _state = new POPPropertyAnimationState(self); +} + +#pragma mark - Properties + +DEFINE_RW_FLAG(POPPropertyAnimationState, additive, isAdditive, setAdditive:); +DEFINE_RW_PROPERTY(POPPropertyAnimationState, roundingFactor, setRoundingFactor:, CGFloat); +DEFINE_RW_PROPERTY(POPPropertyAnimationState, clampMode, setClampMode:, NSUInteger); +DEFINE_RW_PROPERTY_OBJ(POPPropertyAnimationState, property, setProperty:, POPAnimatableProperty*, ((POPPropertyAnimationState*)_state)->updatedDynamicsThreshold();); +DEFINE_RW_PROPERTY_OBJ_COPY(POPPropertyAnimationState, progressMarkers, setProgressMarkers:, NSArray*, ((POPPropertyAnimationState*)_state)->updatedProgressMarkers();); + +- (id)fromValue +{ + return POPBox(__state->fromVec, __state->valueType); +} + +- (void)setFromValue:(id)aValue +{ + POPPropertyAnimationState *s = __state; + VectorRef vec = POPUnbox(aValue, s->valueType, s->valueCount, YES); + if (!vec_equal(vec, s->fromVec)) { + s->fromVec = vec; + + if (s->tracing) { + [s->tracer updateFromValue:aValue]; + } + } +} + +- (id)toValue +{ + return POPBox(__state->toVec, __state->valueType); +} + +- (void)setToValue:(id)aValue +{ + POPPropertyAnimationState *s = (POPPropertyAnimationState *)POPAnimationGetState(self); + VectorRef vec = POPUnbox(aValue, s->valueType, s->valueCount, YES); + + if (!vec_equal(vec, s->toVec)) { + s->toVec = vec; + + // invalidate to dependent state + s->didReachToValue = false; + s->distanceVec = NULL; + + if (s->tracing) { + [s->tracer updateToValue:aValue]; + } + + // automatically unpause active animations + if (s->active && s->paused) { + s->setPaused(false); + } + } +} + +- (id)currentValue +{ + return POPBox(__state->currentValue(), __state->valueType); +} + +#pragma mark - Utility + +- (void)_appendDescription:(NSMutableString *)s debug:(BOOL)debug +{ + [s appendFormat:@"; from = %@; to = %@", describe(__state->fromVec), describe(__state->toVec)]; + + if (_state->active) + [s appendFormat:@"; currentValue = %@", describe(__state->currentValue())]; + + if (__state->velocityVec && 0 != __state->velocityVec->norm()) + [s appendFormat:@"; velocity = %@", describe(__state->velocityVec)]; + + if (!self.removedOnCompletion) + [s appendFormat:@"; removedOnCompletion = %@", POPStringFromBOOL(self.removedOnCompletion)]; + + if (__state->progressMarkers) + [s appendFormat:@"; progressMarkers = [%@]", [__state->progressMarkers componentsJoinedByString:@", "]]; + + if (_state->active) + [s appendFormat:@"; progress = %f", __state->progress]; +} + +@end diff --git a/pop/POPPropertyAnimationInternal.h b/pop/POPPropertyAnimationInternal.h new file mode 100644 index 00000000..ff742ff5 --- /dev/null +++ b/pop/POPPropertyAnimationInternal.h @@ -0,0 +1,358 @@ +/** + 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 "POPAnimationInternal.h" +#import "POPPropertyAnimation.h" + +static void clampValue(CGFloat &value, CGFloat fromValue, CGFloat toValue, NSUInteger clamp) +{ + BOOL increasing = (toValue > fromValue); + + // Clamp start of animation. + if ((kPOPAnimationClampStart & clamp) && + ((increasing && (value < fromValue)) || (!increasing && (value > fromValue)))) { + value = fromValue; + } + + // Clamp end of animation. + if ((kPOPAnimationClampEnd & clamp) && + ((increasing && (value > toValue)) || (!increasing && (value < toValue)))) { + value = toValue; + } +} + +struct _POPPropertyAnimationState : _POPAnimationState +{ + POPAnimatableProperty *property; + POPValueType valueType; + NSUInteger valueCount; + VectorRef fromVec; + VectorRef toVec; + VectorRef currentVec; + VectorRef previousVec; + VectorRef previous2Vec; + VectorRef velocityVec; + VectorRef distanceVec; + CGFloat roundingFactor; + NSUInteger clampMode; + NSArray *progressMarkers; + POPProgressMarker *progressMarkerState; + NSUInteger progressMarkerCount; + NSUInteger nextProgressMarkerIdx; + CGFloat dynamicsThreshold; + + _POPPropertyAnimationState(id __unsafe_unretained anim) : _POPAnimationState(anim), + property(nil), + valueType((POPValueType)0), + valueCount(nil), + fromVec(nullptr), + toVec(nullptr), + currentVec(nullptr), + previousVec(nullptr), + previous2Vec(nullptr), + velocityVec(nullptr), + distanceVec(nullptr), + roundingFactor(0), + clampMode(0), + progressMarkers(nil), + progressMarkerState(nil), + progressMarkerCount(0), + nextProgressMarkerIdx(0), + dynamicsThreshold(0) + { + type = kPOPAnimationBasic; + } + + ~_POPPropertyAnimationState() + { + if (progressMarkerState) { + free(progressMarkerState); + progressMarkerState = NULL; + } + } + + bool canProgress() { + return hasValue(); + } + + bool shouldRound() { + return 0 != roundingFactor; + } + + bool hasValue() { + return 0 != valueCount; + } + + bool isDone() { + // inherit done + if (_POPAnimationState::isDone()) { + return true; + } + + // consider an animation with no values done + if (!hasValue() && !isCustom()) { + return true; + } + + return false; + } + + // returns a copy of the currentVec, rounding if needed + VectorRef currentValue() { + if (!shouldRound()) { + return VectorRef(Vector::new_vector(currentVec.get())); + } else { + VectorRef vec = VectorRef(Vector::new_vector(currentVec.get())); + vec->subRound(1 / roundingFactor); + return vec; + } + } + + void resetProgressMarkerState() + { + for (NSUInteger idx = 0; idx < progressMarkerCount; idx++) + progressMarkerState[idx].reached = false; + + nextProgressMarkerIdx = 0; + } + + void updatedProgressMarkers() + { + if (progressMarkerState) { + free(progressMarkerState); + progressMarkerState = NULL; + } + + progressMarkerCount = progressMarkers.count; + + if (0 != progressMarkerCount) { + progressMarkerState = (POPProgressMarker *)malloc(progressMarkerCount * sizeof(POPProgressMarker)); + [progressMarkers enumerateObjectsUsingBlock:^(NSNumber *progressMarker, NSUInteger idx, BOOL *stop) { + progressMarkerState[idx].reached = false; + progressMarkerState[idx].progress = [progressMarker floatValue]; + }]; + } + + nextProgressMarkerIdx = 0; + } + + virtual void updatedDynamicsThreshold() + { + dynamicsThreshold = property.threshold; + } + + bool advanceProgress(CGFloat p) + { + bool advanced = progress != p; + if (advanced) { + progress = p; + NSUInteger count = valueCount; + VectorRef outVec(Vector::new_vector(count, NULL)); + + if (1.0 == progress) { + if (outVec && toVec) { + *outVec = *toVec; + } + } else { + interpolate_vector(count, vec_data(outVec), vec_data(fromVec), vec_data(toVec), progress); + } + + currentVec = outVec; + clampCurrentValue(); + delegateProgress(); + } + return advanced; + } + + void computeProgress() { + if (!canProgress()) { + return; + } + + static ComputeProgressFunctor func; + Vector4r v = vector4(currentVec); + Vector4r f = vector4(fromVec); + Vector4r t = vector4(toVec); + progress = func(v, f, t); + } + + void delegateProgress() { + if (!canProgress()) { + return; + } + + if (delegateDidProgress && progressMarkerState) { + + while (nextProgressMarkerIdx < progressMarkerCount) { + if (progress < progressMarkerState[nextProgressMarkerIdx].progress) + break; + + if (!progressMarkerState[nextProgressMarkerIdx].reached) { + ActionEnabler enabler; + [delegate pop_animation:self didReachProgress:progressMarkerState[nextProgressMarkerIdx].progress]; + progressMarkerState[nextProgressMarkerIdx].reached = true; + } + + nextProgressMarkerIdx++; + } + } + + if (!didReachToValue) { + bool didReachToValue = false; + if (0 == valueCount) { + didReachToValue = true; + } else { + Vector4r distance = toVec->vector4r(); + distance -= currentVec->vector4r(); + + if (0 == distance.squaredNorm()) { + didReachToValue = true; + } else { + // components + if (distanceVec) { + didReachToValue = true; + const CGFloat *distanceValues = distanceVec->data(); + for (NSUInteger idx = 0; idx < valueCount; idx++) { + didReachToValue &= signbit(distance[idx]) != signbit(distanceValues[idx]); + } + } + } + } + + if (didReachToValue) { + handleDidReachToValue(); + } + } + } + + void handleDidReachToValue() { + didReachToValue = true; + + if (delegateDidReachToValue) { + ActionEnabler enabler; + [delegate pop_animationDidReachToValue:self]; + } + + if (tracing) { + [tracer didReachToValue:POPBox(currentValue(), valueType, true)]; + } + } + + void readObjectValue(VectorRef *ptrVec, id obj) + { + // use current object value as from value + pop_animatable_read_block read = property.readBlock; + if (NULL != read) { + + Vector4r vec = read_values(read, obj, valueCount); + *ptrVec = VectorRef(Vector::new_vector(valueCount, vec)); + + if (tracing) { + [tracer readPropertyValue:POPBox(*ptrVec, valueType, true)]; + } + } + } + + virtual void willRun(bool started, id obj) { + // ensure from value initialized + if (NULL == fromVec) { + readObjectValue(&fromVec, obj); + } + + // ensure to value initialized + if (NULL == toVec) { + // compute decay to value + if (kPOPAnimationDecay == type) { + [self toValue]; + } else { + // read to value + readObjectValue(&toVec, obj); + } + } + + // handle one time value initialization on start + if (started) { + + // initialize current vec + if (!currentVec) { + currentVec = VectorRef(Vector::new_vector(valueCount, NULL)); + + // initialize current value with from value + // only do this on initial creation to avoid overwriting current value + // on paused animation continuation + if (currentVec && fromVec) { + *currentVec = *fromVec; + } + } + + // ensure velocity values + if (!velocityVec) { + velocityVec = VectorRef(Vector::new_vector(valueCount, NULL)); + } + } + + // ensure distance value initialized + // depends on current value set on one time start + if (NULL == distanceVec) { + + // not yet started animations may not have from value + VectorRef fromVec2 = NULL != currentVec ? currentVec : fromVec; + + if (fromVec2 && toVec) { + Vector4r distance = toVec->vector4r(); + distance -= fromVec2->vector4r(); + + if (0 != distance.squaredNorm()) { + distanceVec = VectorRef(Vector::new_vector(valueCount, distance)); + } + } + } + } + + virtual void reset(bool all) { + _POPAnimationState::reset(all); + + if (all) { + currentVec = NULL; + previousVec = NULL; + previous2Vec = NULL; + } + progress = 0; + resetProgressMarkerState(); + didReachToValue = false; + distanceVec = NULL; + } + + void clampCurrentValue(NSUInteger clamp) + { + if (kPOPAnimationClampNone == clamp) + return; + + // Clamp all vector values + CGFloat *currentValues = currentVec->data(); + const CGFloat *fromValues = fromVec->data(); + const CGFloat *toValues = toVec->data(); + + for (NSUInteger idx = 0; idx < valueCount; idx++) { + clampValue(currentValues[idx], fromValues[idx], toValues[idx], clamp); + } + } + + void clampCurrentValue() + { + clampCurrentValue(clampMode); + } +}; + +typedef struct _POPPropertyAnimationState POPPropertyAnimationState; + +@interface POPPropertyAnimation () + +@end + diff --git a/pop/POPSpringAnimation.h b/pop/POPSpringAnimation.h new file mode 100644 index 00000000..75c3babf --- /dev/null +++ b/pop/POPSpringAnimation.h @@ -0,0 +1,67 @@ +/** + 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 + +/** + @abstract A concrete spring animation class. + @discussion Animation is achieved through modeling spring dynamics. + */ +@interface POPSpringAnimation : POPPropertyAnimation + +/** + @abstract The designated initializer. + @returns An instance of a spring animation. + */ ++ (instancetype)animation; + +/** + @abstract Convenience initializer that returns an animation with animatable property of name. + @param name The name of the animatable property. + @returns An instance of a spring animation configured with specified animatable property. + */ ++ (instancetype)animationWithPropertyNamed:(NSString *)name; + +/** + @abstract The current velocity value. + @discussion Set before animation start to account for initial velocity. Expressed in change of value units per second. + */ +@property (copy, nonatomic) id velocity; + +/** + @abstract The effective bounciness. + @discussion Use in conjunction with 'springSpeed' to change animation effect. Values are converted into corresponding dynamics constants. Defined as a value in the range [0, 20]. Defaults to 4. + */ +@property (assign, nonatomic) CGFloat springBounciness; + +/** + @abstract The effective speed. + @discussion Use in conjunction with 'springBounciness' to change animation effect. Values are converted into corresponding dynamics constants. Defined as a value in the range [0, 20]. Defaults to 12. + */ +@property (assign, nonatomic) CGFloat springSpeed; + +/** + @abstract The tension used in the dynamics simulation. + @discussion Can be used over bounciness and speed for finer grain tweaking of animation effect. + */ +@property (assign, nonatomic) CGFloat dynamicsTension; + +/** + @abstract The friction used in the dynamics simulation. + @discussion Can be used over bounciness and speed for finer grain tweaking of animation effect. + */ +@property (assign, nonatomic) CGFloat dynamicsFriction; + +/** + @abstract The mass used in the dynamics simulation. + @discussion Can be used over bounciness and speed for finer grain tweaking of animation effect. + */ +@property (assign, nonatomic) CGFloat dynamicsMass; + +@end diff --git a/pop/POPSpringAnimation.mm b/pop/POPSpringAnimation.mm new file mode 100644 index 00000000..10cb510a --- /dev/null +++ b/pop/POPSpringAnimation.mm @@ -0,0 +1,164 @@ +/** + 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 "POPSpringAnimationInternal.h" + +@implementation POPSpringAnimation + +#pragma mark - Lifecycle + +#undef __state +#define __state ((POPSpringAnimationState *)_state) + ++ (instancetype)animation +{ + return [[self alloc] init]; +} + ++ (instancetype)animationWithPropertyNamed:(NSString *)aName +{ + POPSpringAnimation *anim = [self animation]; + anim.property = [POPAnimatableProperty propertyWithName:aName]; + return anim; +} + +- (void)_initState +{ + _state = new POPSpringAnimationState(self); +} + +- (id)init +{ + self = [super _init]; + if (nil != self) { + __state->solver = new SpringSolver4d(1, 1, 1); + __state->updatedDynamicsThreshold(); + __state->updatedBouncinessAndSpeed(); + } + return self; +} + +- (void)dealloc +{ + if (__state) { + delete __state->solver; + __state->solver = NULL; + } +} + +#pragma mark - Properties + +- (id)velocity +{ + return POPBox(__state->velocityVec, __state->valueType); +} + +- (void)setVelocity:(id)aValue +{ + POPPropertyAnimationState *s = __state; + VectorRef vec = POPUnbox(aValue, s->valueType, s->valueCount, YES); + if (!vec_equal(vec, s->velocityVec)) { + s->velocityVec = vec; + + if (s->tracing) { + [s->tracer updateVelocity:aValue]; + } + } +} + +DEFINE_RW_PROPERTY(POPSpringAnimationState, dynamicsTension, setDynamicsTension:, CGFloat, [self _updatedDynamicsTension];); +DEFINE_RW_PROPERTY(POPSpringAnimationState, dynamicsFriction, setDynamicsFriction:, CGFloat, [self _updatedDynamicsFriction];); +DEFINE_RW_PROPERTY(POPSpringAnimationState, dynamicsMass, setDynamicsMass:, CGFloat, [self _updatedDynamicsMass];); + +FB_PROPERTY_GET(POPSpringAnimationState, springSpeed, CGFloat); +- (void)setSpringSpeed:(CGFloat)aFloat +{ + POPSpringAnimationState *s = __state; + if (s->userSpecifiedDynamics || aFloat != s->springSpeed) { + s->springSpeed = aFloat; + s->userSpecifiedDynamics = false; + s->updatedBouncinessAndSpeed(); + if (s->tracing) { + [s->tracer updateSpeed:aFloat]; + } + } +} + +FB_PROPERTY_GET(POPSpringAnimationState, springBounciness, CGFloat); +- (void)setSpringBounciness:(CGFloat)aFloat +{ + POPSpringAnimationState *s = __state; + if (s->userSpecifiedDynamics || aFloat != s->springBounciness) { + s->springBounciness = aFloat; + s->userSpecifiedDynamics = false; + s->updatedBouncinessAndSpeed(); + if (s->tracing) { + [s->tracer updateBounciness:aFloat]; + } + } +} + +- (SpringSolver4d *)solver +{ + return __state->solver; +} + +- (void)setSolver:(SpringSolver4d *)aSolver +{ + if (aSolver != __state->solver) { + if (__state->solver) { + delete(__state->solver); + } + __state->solver = aSolver; + } +} + +#pragma mark - Utility + +- (void)_updatedDynamicsTension +{ + __state->userSpecifiedDynamics = true; + if(__state->tracing) { + [__state->tracer updateTension:__state->dynamicsTension]; + } + __state->updatedDynamics(); +} + +- (void)_updatedDynamicsFriction +{ + __state->userSpecifiedDynamics = true; + if(__state->tracing) { + [__state->tracer updateFriction:__state->dynamicsFriction]; + } + __state->updatedDynamics(); +} + +- (void)_updatedDynamicsMass +{ + __state->userSpecifiedDynamics = true; + if(__state->tracing) { + [__state->tracer updateMass:__state->dynamicsMass]; + } + __state->updatedDynamics(); +} + +- (void)_appendDescription:(NSMutableString *)s debug:(BOOL)debug +{ + [super _appendDescription:s debug:debug]; + + if (debug) { + if (_state->userSpecifiedDynamics) { + [s appendFormat:@"; dynamics = (tension:%f, friction:%f, mass:%f)", __state->dynamicsTension, __state->dynamicsFriction, __state->dynamicsMass]; + } else { + [s appendFormat:@"; bounciness = %f; speed = %f", __state->springBounciness, __state->springSpeed]; + } + } +} + +@end diff --git a/pop/POPSpringAnimationInternal.h b/pop/POPSpringAnimationInternal.h new file mode 100644 index 00000000..82df1ac1 --- /dev/null +++ b/pop/POPSpringAnimationInternal.h @@ -0,0 +1,131 @@ +/** + 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 "POPAnimationExtras.h" +#import "POPPropertyAnimationInternal.h" +#import "POPSpringAnimation.h" + +struct _POPSpringAnimationState : _POPPropertyAnimationState +{ + SpringSolver4d *solver; + CGFloat springSpeed; + CGFloat springBounciness; // normalized springiness + CGFloat dynamicsTension; // tension + CGFloat dynamicsFriction; // friction + CGFloat dynamicsMass; // mass + + _POPSpringAnimationState(id __unsafe_unretained anim) : _POPPropertyAnimationState(anim), + solver(nullptr), + springSpeed(12.), + springBounciness(4.), + dynamicsTension(0), + dynamicsFriction(0), + dynamicsMass(0) + { + type = kPOPAnimationSpring; + } + + bool hasConverged() + { + NSUInteger count = valueCount; + if (shouldRound()) { + return vec_equal(previous2Vec, previousVec) && vec_equal(previousVec, toVec); + } else { + if (!previousVec || !previous2Vec) + return false; + + CGFloat t = dynamicsThreshold / 5; + + const CGFloat *toValues = toVec->data(); + const CGFloat *previousValues = previousVec->data(); + const CGFloat *previous2Values = previous2Vec->data(); + + for (NSUInteger idx = 0; idx < count; idx++) { + if ((fabsf(toValues[idx] - previousValues[idx]) >= t) || (fabsf(previous2Values[idx] - previousValues[idx]) >= t)) { + return false; + } + } + return true; + } + } + + bool isDone() { + if (_POPPropertyAnimationState::isDone()) { + return true; + } + return solver->started() && (hasConverged() || solver->hasConverged()); + } + + void updatedDynamics() + { + if (NULL != solver) { + solver->setConstants(dynamicsTension, dynamicsFriction, dynamicsMass); + } + } + + void updatedDynamicsThreshold() + { + _POPPropertyAnimationState::updatedDynamicsThreshold(); + if (NULL != solver) { + solver->setThreshold(dynamicsThreshold); + } + } + + void updatedBouncinessAndSpeed() { + [POPSpringAnimation convertBounciness:springBounciness speed:springSpeed toTension:&dynamicsTension friction:&dynamicsFriction mass:&dynamicsMass]; + updatedDynamics(); + } + + bool advance(CFTimeInterval time, CFTimeInterval dt, id obj) { + // advance past not yet initialized animations + if (NULL == currentVec) { + return false; + } + + CFTimeInterval localTime = time - startTime; + + Vector4d value = vector4d(currentVec); + Vector4d toValue = vector4d(toVec); + Vector4d velocity = vector4d(velocityVec); + + SSState4d state; + state.p = toValue - value; + + // the solver assumes a spring of size zero + // flip the velocity from user perspective to solver perspective + state.v = velocity * -1; + + solver->advance(state, localTime, dt); + value = toValue - state.p; + + // flip velocity back to user perspective + velocity = state.v * -1; + + *currentVec = value; + + if (velocityVec) { + *velocityVec = velocity; + } + + clampCurrentValue(); + + return true; + } + + virtual void reset(bool all) { + _POPPropertyAnimationState::reset(all); + + if (solver) { + solver->setConstants(dynamicsTension, dynamicsFriction, dynamicsMass); + solver->reset(); + } + } +}; + +typedef struct _POPSpringAnimationState POPSpringAnimationState; diff --git a/pop/POPSpringSolver.h b/pop/POPSpringSolver.h new file mode 100644 index 00000000..76e1cda4 --- /dev/null +++ b/pop/POPSpringSolver.h @@ -0,0 +1,190 @@ +/** + 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 + +#import "POPVector.h" + +namespace POP { + + template + struct SSState + { + T p; + T v; + }; + + template + struct SSDerivative + { + T dp; + T dv; + }; + + typedef SSState SSState4d; + typedef SSDerivative SSDerivative4d; + + const CFTimeInterval solverDt = 0.001f; + const CFTimeInterval maxSolverDt = 30.0f; + + /** + Templated spring solver class. + */ + template + class SpringSolver + { + double _k; // stiffness + double _b; // dampening + double _m; // mass + + double _tp; // threshold + double _tv; // threshold velocity + double _ta; // threshold acceleration + + CFTimeInterval _accumulatedTime; + SSState _lastState; + T _lastDv; + bool _started; + + public: + SpringSolver(double k, double b, double m = 1) : _k(k), _b(b), _m(m), _started(false) + { + _accumulatedTime = 0; + _lastState.p = T::Zero(); + _lastState.v = T::Zero(); + _lastDv = T::Zero(); + setThreshold(1.); + } + + ~SpringSolver() + { + } + + bool started() + { + return _started; + } + + void setConstants(double k, double b, double m) + { + _k = k; + _b = b; + _m = m; + } + + void setThreshold(double t) + { + _tp = t / 2; // half a unit + _tv = 25.0 * t; // 5 units per second, squared for comparison + _ta = 625.0 * t * t; // 5 units per second squared, squared for comparison + } + + T acceleration(const SSState &state, double t) + { + return state.p*(-_k/_m) - state.v*(_b/_m); + } + + SSDerivative evaluate(const SSState &initial, double t) + { + SSDerivative output; + output.dp = initial.v; + output.dv = acceleration(initial, t); + return output; + } + + SSDerivative evaluate(const SSState &initial, double t, double dt, const SSDerivative &d) + { + SSState state; + state.p = initial.p + d.dp*dt; + state.v = initial.v + d.dv*dt; + SSDerivative output; + output.dp = state.v; + output.dv = acceleration(state, t+dt); + return output; + } + + void integrate(SSState &state, double t, double dt) + { + SSDerivative a = evaluate(state, t); + SSDerivative b = evaluate(state, t, dt*0.5, a); + SSDerivative c = evaluate(state, t, dt*0.5, b); + SSDerivative d = evaluate(state, t, dt, c); + + T dpdt = (a.dp + (b.dp + c.dp)*2.0 + d.dp) * (1.0/6.0); + T dvdt = (a.dv + (b.dv + c.dv)*2.0 + d.dv) * (1.0/6.0); + + state.p = state.p + dpdt*dt; + state.v = state.v + dvdt*dt; + + _lastDv = dvdt; + } + + SSState interpolate(const SSState &previous, const SSState ¤t, double alpha) + { + SSState state; + state.p = current.p*alpha + previous.p*(1-alpha); + state.v = current.v*alpha + previous.v*(1-alpha); + return state; + } + + void advance(SSState &state, double t, double dt) + { + _started = true; + + if (dt > maxSolverDt) { + // excessive time step, force shut down + _lastDv = _lastState.v = _lastState.p = T::Zero(); + } else { + _accumulatedTime += dt; + + SSState previousState = state, currentState = state; + while (_accumulatedTime >= solverDt) { + previousState = currentState; + this->integrate(currentState, t, solverDt); + t += solverDt; + _accumulatedTime -= solverDt; + } + CFTimeInterval alpha = _accumulatedTime / solverDt; + _lastState = state = this->interpolate(previousState, currentState, alpha); + } + } + + bool hasConverged() + { + if (!_started) { + return false; + } + + for (int idx = 0; idx < _lastState.p.size(); idx++) { + if (fabs(_lastState.p(idx)) >= _tp) { + return false; + } + } + + return (_lastState.v.squaredNorm() < _tv) && (_lastDv.squaredNorm() < _ta); + } + + void reset() + { + _accumulatedTime = 0; + _lastState.p = T::Zero(); + _lastState.v = T::Zero(); + _lastDv = T::Zero(); + _started = false; + } + }; + + /** + Convenience spring solver type definitions. + */ + typedef SpringSolver SpringSolver2d; + typedef SpringSolver SpringSolver3d; + typedef SpringSolver SpringSolver4d; +} + diff --git a/pop/POPVector.h b/pop/POPVector.h new file mode 100644 index 00000000..05a4a740 --- /dev/null +++ b/pop/POPVector.h @@ -0,0 +1,366 @@ +/** + 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. + */ + +#ifndef __POP__FBVector__ +#define __POP__FBVector__ + +#include +#include + +#import +#import +#import "POPMath.h" + +namespace POP { + + /** Fixed two-size vector class */ + template + struct Vector2 + { + private: + typedef T Vector2::* const _data[2]; + static const _data _v; + + public: + T x; + T y; + + // Zero vector + static const Vector2 Zero() { return Vector2(0); } + + // Constructors + Vector2() {} + explicit Vector2(T v) { x = v; y = v; }; + explicit Vector2(T x0, T y0) : x(x0), y(y0) {}; + explicit Vector2(const CGPoint &p) : x(p.x), y (p.y) {} + explicit Vector2(const CGSize &s) : x(s.width), y (s.height) {} + + // Copy constructor + template explicit Vector2(const Vector2 &v) : x(v.x), y(v.y) {} + + // Index operators + const T& operator[](size_t i) const { return this->*_v[i]; } + T& operator[](size_t i) { return this->*_v[i]; } + const T& operator()(size_t i) const { return this->*_v[i]; } + T& operator()(size_t i) { return this->*_v[i]; } + + // Backing data + T * data() { return &(this->*_v[0]); } + const T * data() const { return &(this->*_v[0]); } + + // Size + inline size_t size() const { return 2; } + + // Assignment + Vector2 &operator= (T v) { x = v; y = v; return *this;} + template Vector2 &operator= (const Vector2 &v) { x = v.x; y = v.y; return *this;} + + // Negation + Vector2 operator- (void) const { return Vector2(-x, -y); } + + // Equality + bool operator== (T v) const { return (x == v && y == v); } + bool operator== (const Vector2 &v) const { return (x == v.x && y == v.y); } + + // Inequality + bool operator!= (T v) const {return (x != v || y != v); } + bool operator!= (const Vector2 &v) const { return (x != v.x || y != v.y); } + + // Scalar Math + Vector2 operator+ (T v) const { return Vector2(x + v, y + v); } + Vector2 operator- (T v) const { return Vector2(x - v, y - v); } + Vector2 operator* (T v) const { return Vector2(x * v, y * v); } + Vector2 operator/ (T v) const { return Vector2(x / v, y / v); } + Vector2 &operator+= (T v) { x += v; y += v; return *this; }; + Vector2 &operator-= (T v) { x -= v; y -= v; return *this; }; + Vector2 &operator*= (T v) { x *= v; y *= v; return *this; }; + Vector2 &operator/= (T v) { x /= v; y /= v; return *this; }; + + // Vector Math + Vector2 operator+ (const Vector2 &v) const { return Vector2(x + v.x, y + v.y); } + Vector2 operator- (const Vector2 &v) const { return Vector2(x - v.x, y - v.y); } + Vector2 &operator+= (const Vector2 &v) { x += v.x; y += v.y; return *this; }; + Vector2 &operator-= (const Vector2 &v) { x -= v.x; y -= v.y; return *this; }; + + // Norms + CGFloat norm() const { return sqrtr(squaredNorm()); } + CGFloat squaredNorm() const { return x * x + y * y; } + + // Cast + template Vector2 cast() const { return Vector2(x, y); } + CGPoint cg_point() const { return CGPointMake(x, y); }; + }; + + template + const typename Vector2::_data Vector2::_v = { &Vector2::x, &Vector2::y }; + + /** Fixed three-size vector class */ + template + struct Vector3 + { + private: + typedef T Vector3::* const _data[3]; + static const _data _v; + + public: + T x; + T y; + T z; + + // Zero vector + static const Vector3 Zero() { return Vector3(0); }; + + // Constructors + Vector3() {} + explicit Vector3(T v) : x(v), y(v), z(v) {}; + explicit Vector3(T x0, T y0, T z0) : x(x0), y(y0), z(z0) {}; + + // Copy constructor + template explicit Vector3(const Vector3 &v) : x(v.x), y(v.y), z(v.z) {} + + // Index operators + const T& operator[](size_t i) const { return this->*_v[i]; } + T& operator[](size_t i) { return this->*_v[i]; } + const T& operator()(size_t i) const { return this->*_v[i]; } + T& operator()(size_t i) { return this->*_v[i]; } + + // Backing data + T * data() { return &(this->*_v[0]); } + const T * data() const { return &(this->*_v[0]); } + + // Size + inline size_t size() const { return 3; } + + // Assignment + Vector3 &operator= (T v) { x = v; y = v; z = v; return *this;} + template Vector3 &operator= (const Vector3 &v) { x = v.x; y = v.y; z = v.z; return *this;} + + // Negation + Vector3 operator- (void) const { return Vector3(-x, -y, -z); } + + // Equality + bool operator== (T v) const { return (x == v && y == v && z = v); } + bool operator== (const Vector3 &v) const { return (x == v.x && y == v.y && z == v.z); } + + // Inequality + bool operator!= (T v) const {return (x != v || y != v || z != v); } + bool operator!= (const Vector3 &v) const { return (x != v.x || y != v.y || z != v.z); } + + // Scalar Math + Vector3 operator+ (T v) const { return Vector3(x + v, y + v, z + v); } + Vector3 operator- (T v) const { return Vector3(x - v, y - v, z - v); } + Vector3 operator* (T v) const { return Vector3(x * v, y * v, z * v); } + Vector3 operator/ (T v) const { return Vector3(x / v, y / v, z / v); } + Vector3 &operator+= (T v) { x += v; y += v; z += v; return *this; }; + Vector3 &operator-= (T v) { x -= v; y -= v; z -= v; return *this; }; + Vector3 &operator*= (T v) { x *= v; y *= v; z *= v; return *this; }; + Vector3 &operator/= (T v) { x /= v; y /= v; z /= v; return *this; }; + + // Vector Math + Vector3 operator+ (const Vector3 &v) const { return Vector3(x + v.x, y + v.y, z + v.z); } + Vector3 operator- (const Vector3 &v) const { return Vector3(x - v.x, y - v.y, z - v.z); } + Vector3 &operator+= (const Vector3 &v) { x += v.x; y += v.y; z += v.z; return *this; }; + Vector3 &operator-= (const Vector3 &v) { x -= v.x; y -= v.y; z -= v.z; return *this; }; + + // Norms + CGFloat norm() const { return sqrtr(squaredNorm()); } + CGFloat squaredNorm() const { return x * x + y * y + z * z; } + + // Cast + template Vector3 cast() const { return Vector3(x, y, z); } + }; + + template + const typename Vector3::_data Vector3::_v = { &Vector3::x, &Vector3::y, &Vector3::z }; + + /** Fixed four-size vector class */ + template + struct Vector4 + { + private: + typedef T Vector4::* const _data[4]; + static const _data _v; + + public: + T x; + T y; + T z; + T w; + + // Zero vector + static const Vector4 Zero() { return Vector4(0); }; + + // Constructors + Vector4() {} + explicit Vector4(T v) : x(v), y(v), z(v), w(v) {}; + explicit Vector4(T x0, T y0, T z0, T w0) : x(x0), y(y0), z(z0), w(w0) {}; + + // Copy constructor + template explicit Vector4(const Vector4 &v) : x(v.x), y(v.y), z(v.z), w(v.w) {} + + // Index operators + const T& operator[](size_t i) const { return this->*_v[i]; } + T& operator[](size_t i) { return this->*_v[i]; } + const T& operator()(size_t i) const { return this->*_v[i]; } + T& operator()(size_t i) { return this->*_v[i]; } + + // Backing data + T * data() { return &(this->*_v[0]); } + const T * data() const { return &(this->*_v[0]); } + + // Size + inline size_t size() const { return 4; } + + // Assignment + Vector4 &operator= (T v) { x = v; y = v; z = v; w = v; return *this;} + template Vector4 &operator= (const Vector4 &v) { x = v.x; y = v.y; z = v.z; w = v.w; return *this;} + + // Negation + Vector4 operator- (void) const { return Vector4(-x, -y, -z, -w); } + + // Equality + bool operator== (T v) const { return (x == v && y == v && z = v, w = v); } + bool operator== (const Vector4 &v) const { return (x == v.x && y == v.y && z == v.z && w = v.w); } + + // Inequality + bool operator!= (T v) const {return (x != v || y != v || z != v || w != v); } + bool operator!= (const Vector4 &v) const { return (x != v.x || y != v.y || z != v.z || w != v.w); } + + // Scalar Math + Vector4 operator+ (T v) const { return Vector4(x + v, y + v, z + v, w + v); } + Vector4 operator- (T v) const { return Vector4(x - v, y - v, z - v, w - v); } + Vector4 operator* (T v) const { return Vector4(x * v, y * v, z * v, w * v); } + Vector4 operator/ (T v) const { return Vector4(x / v, y / v, z / v, w / v); } + Vector4 &operator+= (T v) { x += v; y += v; z += v; w += v; return *this; }; + Vector4 &operator-= (T v) { x -= v; y -= v; z -= v; w -= v; return *this; }; + Vector4 &operator*= (T v) { x *= v; y *= v; z *= v; w *= v; return *this; }; + Vector4 &operator/= (T v) { x /= v; y /= v; z /= v; w /= v; return *this; }; + + // Vector Math + Vector4 operator+ (const Vector4 &v) const { return Vector4(x + v.x, y + v.y, z + v.z, w + v.w); } + Vector4 operator- (const Vector4 &v) const { return Vector4(x - v.x, y - v.y, z - v.z, w - v.w); } + Vector4 &operator+= (const Vector4 &v) { x += v.x; y += v.y; z += v.z; w += v.w; return *this; }; + Vector4 &operator-= (const Vector4 &v) { x -= v.x; y -= v.y; z -= v.z; w -= v.w; return *this; }; + + // Norms + CGFloat norm() const { return sqrtr(squaredNorm()); } + CGFloat squaredNorm() const { return x * x + y * y + z * z + w * w; } + + // Cast + template Vector4 cast() const { return Vector4(x, y, z, w); } + }; + + template + const typename Vector4::_data Vector4::_v = { &Vector4::x, &Vector4::y, &Vector4::z, &Vector4::w }; + + /** Convenience typedefs */ + typedef Vector2 Vector2f; + typedef Vector2 Vector2d; + typedef Vector2 Vector2r; + typedef Vector3 Vector3f; + typedef Vector3 Vector3d; + typedef Vector3 Vector3r; + typedef Vector4 Vector4f; + typedef Vector4 Vector4d; + typedef Vector4 Vector4r; + + /** Variable-sized vector class */ + class Vector + { + size_t _count; + CGFloat *_values; + + private: + Vector(size_t); + Vector(const Vector& other); + + public: + ~Vector(); + + // Creates a new vector instance of count with values. Initializing a vector of size 0 returns NULL. + static Vector *new_vector(NSUInteger count, const CGFloat *values); + + // Creates a new vector given a pointer to another. Can return NULL. + static Vector *new_vector(const Vector * const other); + + // Creates a variable size vector given a static vector and count. + static Vector *new_vector(NSUInteger count, Vector4r vec); + + // Size of vector + NSUInteger size() const { return _count; } + + // Returns array of values + CGFloat *data () { return _values; } + const CGFloat *data () const { return _values; }; + + // Vector2r support + Vector2r vector2r() const; + + // Vector4r support + Vector4r vector4r() const; + + // CGFloat support + static Vector *new_cg_float(CGFloat f); + + // CGPoint support + CGPoint cg_point() const; + static Vector *new_cg_point(const CGPoint &p); + + // CGSize support + CGSize cg_size() const; + static Vector *new_cg_size(const CGSize &s); + + // CGRect support + CGRect cg_rect() const; + static Vector *new_cg_rect(const CGRect &r); + + // CGAffineTransform support + CGAffineTransform cg_affine_transform() const; + static Vector *new_cg_affine_transform(const CGAffineTransform &t); + + // CGColorRef support + CGColorRef cg_color() const CF_RETURNS_RETAINED; + static Vector *new_cg_color(CGColorRef color); + + // operator overloads + CGFloat &operator[](size_t i) const { + NSCAssert(size() > i, @"unexpected vector size:%lu", (unsigned long)size()); + return _values[i]; + } + + // Returns the mathematical length + CGFloat norm() const; + CGFloat squaredNorm() const; + + // Round to nearest sub + void subRound(CGFloat sub); + + // Returns string description + NSString * const toString() const; + + // Operator overloads + template Vector& operator= (const Vector4& other) { + size_t count = MIN(_count, other.size()); + for (size_t i = 0; i < count; i++) { + _values[i] = other[i]; + } + return *this; + } + Vector& operator= (const Vector& other); + void swap(Vector &first, Vector &second); + bool operator==(const Vector &other) const; + bool operator!=(const Vector &other) const; + }; + + /** Convenience typedefs */ + typedef std::shared_ptr VectorRef; + typedef std::shared_ptr VectorConstRef; + +} +#endif /* defined(__POP__FBVector__) */ diff --git a/pop/POPVector.mm b/pop/POPVector.mm new file mode 100644 index 00000000..f9bf1f5d --- /dev/null +++ b/pop/POPVector.mm @@ -0,0 +1,283 @@ +/** + 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 "POPVector.h" +#import "POPAnimationRuntime.h" +#import "POPCGUtils.h" + +namespace POP +{ + + Vector::Vector(const size_t count) + { + _count = count; + _values = 0 != count ? (CGFloat *)calloc(count, sizeof(CGFloat)) : NULL; + } + + Vector::Vector(const Vector& other) + { + _count = other.size(); + _values = 0 != _count ? (CGFloat *)calloc(_count, sizeof(CGFloat)) : NULL; + if (0 != _count) { + memcpy(_values, other.data(), _count * sizeof(CGFloat)); + } + } + + Vector::~Vector() + { + if (NULL != _values) { + free(_values); + _values = NULL; + } + _count = 0; + } + + void Vector::swap(Vector &first, Vector &second) + { + using std::swap; + swap(first._count, second._count); + swap(first._values, second._values); + } + + Vector& Vector::operator=(const Vector& other) + { + Vector temp(other); + swap(*this, temp); + return *this; + } + + bool Vector::operator==(const Vector &other) const { + if (_count != other.size()) { + return false; + } + + const CGFloat * const values = other.data(); + + for (NSUInteger idx = 0; idx < _count; idx++) { + if (_values[idx] != values[idx]) { + return false; + } + } + + return true; + } + + bool Vector::operator!=(const Vector &other) const { + if (_count == other.size()) { + return false; + } + + const CGFloat * const values = other.data(); + + for (NSUInteger idx = 0; idx < _count; idx++) { + if (_values[idx] != values[idx]) { + return false; + } + } + + return true; + } + + Vector *Vector::new_vector(NSUInteger count, const CGFloat *values) + { + if (0 == count) { + return NULL; + } + + Vector *v = new Vector(count); + if (NULL != values) { + memcpy(v->_values, values, count * sizeof(CGFloat)); + } + return v; + } + + Vector *Vector::new_vector(const Vector * const other) + { + if (NULL == other) { + return NULL; + } + + return Vector::new_vector(other->size(), other->data()); + } + + Vector *Vector::new_vector(NSUInteger count, Vector4r vec) + { + if (0 == count) { + return NULL; + } + + Vector *v = new Vector(count); + + NSCAssert(count <= 4, @"unexpected count %lu", (unsigned long)count); + for (NSUInteger i = 0; i < MIN(count, 4); i++) { + v->_values[i] = vec[i]; + } + + return v; + } + + Vector4r Vector::vector4r() const + { + Vector4r v = Vector4r::Zero(); + for (int i = 0; i < _count; i++) { + v(i) = _values[i]; + } + return v; + } + + Vector2r Vector::vector2r() const + { + Vector2r v = Vector2r::Zero(); + if (_count > 0) v(0) = _values[0]; + if (_count > 1) v(1) = _values[1]; + return v; + } + + Vector *Vector::new_cg_float(CGFloat f) + { + Vector *v = new Vector(1); + v->_values[0] = f; + return v; + } + + CGPoint Vector::cg_point () const + { + Vector2r v = vector2r(); + return CGPointMake(v(0), v(1)); + } + + Vector *Vector::new_cg_point(const CGPoint &p) + { + Vector *v = new Vector(2); + v->_values[0] = p.x; + v->_values[1] = p.y; + return v; + } + + CGSize Vector::cg_size () const + { + Vector2r v = vector2r(); + return CGSizeMake(v(0), v(1)); + } + + Vector *Vector::new_cg_size(const CGSize &s) + { + Vector *v = new Vector(2); + v->_values[0] = s.width; + v->_values[1] = s.height; + return v; + } + + CGRect Vector::cg_rect() const + { + return _count < 4 ? CGRectZero : CGRectMake(_values[0], _values[1], _values[2], _values[3]); + } + + Vector *Vector::new_cg_rect(const CGRect &r) + { + Vector *v = new Vector(4); + v->_values[0] = r.origin.x; + v->_values[1] = r.origin.y; + v->_values[2] = r.size.width; + v->_values[3] = r.size.height; + return v; + } + + CGAffineTransform Vector::cg_affine_transform() const + { + if (_count < 6) { + return CGAffineTransformIdentity; + } + + NSCAssert(size() >= 6, @"unexpected vector size:%lu", (unsigned long)size()); + CGAffineTransform t; + t.a = _values[0]; + t.b = _values[1]; + t.c = _values[2]; + t.d = _values[3]; + t.tx = _values[4]; + t.ty = _values[5]; + return t; + } + + Vector *Vector::new_cg_affine_transform(const CGAffineTransform &t) + { + Vector *v = new Vector(4); + v->_values[0] = t.a; + v->_values[1] = t.b; + v->_values[2] = t.c; + v->_values[3] = t.d; + v->_values[4] = t.tx; + v->_values[5] = t.ty; + return v; + } + + CGColorRef Vector::cg_color() const + { + if (_count < 4) { + return NULL; + } + return POPCGColorRGBACreate(_values); + } + + Vector *Vector::new_cg_color(CGColorRef color) + { + CGFloat rgba[4]; + POPCGColorGetRGBAComponents(color, rgba); + return new_vector(4, rgba); + } + + void Vector::subRound(CGFloat sub) + { + for (NSUInteger idx = 0; idx < _count; idx++) { + _values[idx] = POPSubRound(_values[idx], sub); + } + } + + CGFloat Vector::norm() const + { + return sqrtr(squaredNorm()); + } + + CGFloat Vector::squaredNorm() const + { + CGFloat d = 0; + for (NSUInteger idx = 0; idx < _count; idx++) { + d += (_values[idx] * _values[idx]); + } + return d; + } + + NSString * const Vector::toString() const + { + if (0 == _count) + return @"()"; + + if (1 == _count) + return [NSString stringWithFormat:@"%f", _values[0]]; + + if (2 == _count) + return [NSString stringWithFormat:@"(%.3f, %.3f)", _values[0], _values[1]]; + + NSMutableString *s = [NSMutableString stringWithCapacity:10]; + + for (NSUInteger idx = 0; idx < _count; idx++) { + if (0 == idx) { + [s appendFormat:@"[%.3f", _values[idx]]; + } else if (idx == _count - 1) { + [s appendFormat:@", %.3f]", _values[idx]]; + } else { + [s appendFormat:@", %.3f", _values[idx]]; + } + } + + return s; + + } +} diff --git a/pop/WebCore/FloatConversion.h b/pop/WebCore/FloatConversion.h new file mode 100644 index 00000000..4a161660 --- /dev/null +++ b/pop/WebCore/FloatConversion.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef FloatConversion_h +#define FloatConversion_h + +#include + +namespace WebCore { + + template + float narrowPrecisionToFloat(T); + + template<> + inline float narrowPrecisionToFloat(double number) + { + return static_cast(number); + } + + template + CGFloat narrowPrecisionToCGFloat(T); + + template<> + inline CGFloat narrowPrecisionToCGFloat(double number) + { + return static_cast(number); + } + +} // namespace WebCore + +#endif // FloatConversion_h diff --git a/pop/WebCore/TransformationMatrix.cpp b/pop/WebCore/TransformationMatrix.cpp new file mode 100644 index 00000000..cbfd701c --- /dev/null +++ b/pop/WebCore/TransformationMatrix.cpp @@ -0,0 +1,1072 @@ +/* + * Copyright (C) 2005, 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2009 Torch Mobile, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "TransformationMatrix.h" +#include "FloatConversion.h" +#include + +inline double deg2rad(double d) { return d * M_PI / 180.0; } +inline double rad2deg(double r) { return r * 180.0 / M_PI; } +inline double deg2grad(double d) { return d * 400.0 / 360.0; } +inline double grad2deg(double g) { return g * 360.0 / 400.0; } +inline double turn2deg(double t) { return t * 360.0; } +inline double deg2turn(double d) { return d / 360.0; } +inline double rad2grad(double r) { return r * 200.0 / M_PI; } +inline double grad2rad(double g) { return g * M_PI / 200.0; } + +//using namespace std; + +namespace WebCore { + + // + // Supporting Math Functions + // + // This is a set of function from various places (attributed inline) to do things like + // inversion and decomposition of a 4x4 matrix. They are used throughout the code + // + + // + // Adapted from Matrix Inversion by Richard Carling, Graphics Gems . + + // EULA: The Graphics Gems code is copyright-protected. In other words, you cannot claim the text of the code + // as your own and resell it. Using the code is permitted in any program, product, or library, non-commercial + // or commercial. Giving credit is not required, though is a nice gesture. The code comes as-is, and if there + // are any flaws or problems with any Gems code, nobody involved with Gems - authors, editors, publishers, or + // webmasters - are to be held responsible. Basically, don't be a jerk, and remember that anything free comes + // with no guarantee. + + // A clarification about the storage of matrix elements + // + // This class uses a 2 dimensional array internally to store the elements of the matrix. The first index into + // the array refers to the column that the element lies in; the second index refers to the row. + // + // In other words, this is the layout of the matrix: + // + // | m_matrix[0][0] m_matrix[1][0] m_matrix[2][0] m_matrix[3][0] | + // | m_matrix[0][1] m_matrix[1][1] m_matrix[2][1] m_matrix[3][1] | + // | m_matrix[0][2] m_matrix[1][2] m_matrix[2][2] m_matrix[3][2] | + // | m_matrix[0][3] m_matrix[1][3] m_matrix[2][3] m_matrix[3][3] | + + typedef double Vector4[4]; + typedef double Vector3[3]; + + const double SMALL_NUMBER = 1.e-8; + + // inverse(original_matrix, inverse_matrix) + // + // calculate the inverse of a 4x4 matrix + // + // -1 + // A = ___1__ adjoint A + // det A + + // double = determinant2x2(double a, double b, double c, double d) + // + // calculate the determinant of a 2x2 matrix. + + static double determinant2x2(double a, double b, double c, double d) + { + return a * d - b * c; + } + + // double = determinant3x3(a1, a2, a3, b1, b2, b3, c1, c2, c3) + // + // Calculate the determinant of a 3x3 matrix + // in the form + // + // | a1, b1, c1 | + // | a2, b2, c2 | + // | a3, b3, c3 | + + static double determinant3x3(double a1, double a2, double a3, double b1, double b2, double b3, double c1, double c2, double c3) + { + return a1 * determinant2x2(b2, b3, c2, c3) + - b1 * determinant2x2(a2, a3, c2, c3) + + c1 * determinant2x2(a2, a3, b2, b3); + } + + // double = determinant4x4(matrix) + // + // calculate the determinant of a 4x4 matrix. + + static double determinant4x4(const TransformationMatrix::Matrix4& m) + { + // Assign to individual variable names to aid selecting + // correct elements + + double a1 = m[0][0]; + double b1 = m[0][1]; + double c1 = m[0][2]; + double d1 = m[0][3]; + + double a2 = m[1][0]; + double b2 = m[1][1]; + double c2 = m[1][2]; + double d2 = m[1][3]; + + double a3 = m[2][0]; + double b3 = m[2][1]; + double c3 = m[2][2]; + double d3 = m[2][3]; + + double a4 = m[3][0]; + double b4 = m[3][1]; + double c4 = m[3][2]; + double d4 = m[3][3]; + + return a1 * determinant3x3(b2, b3, b4, c2, c3, c4, d2, d3, d4) + - b1 * determinant3x3(a2, a3, a4, c2, c3, c4, d2, d3, d4) + + c1 * determinant3x3(a2, a3, a4, b2, b3, b4, d2, d3, d4) + - d1 * determinant3x3(a2, a3, a4, b2, b3, b4, c2, c3, c4); + } + + // adjoint( original_matrix, inverse_matrix ) + // + // calculate the adjoint of a 4x4 matrix + // + // Let a denote the minor determinant of matrix A obtained by + // ij + // + // deleting the ith row and jth column from A. + // + // i+j + // Let b = (-1) a + // ij ji + // + // The matrix B = (b ) is the adjoint of A + // ij + + static void adjoint(const TransformationMatrix::Matrix4& matrix, TransformationMatrix::Matrix4& result) + { + // Assign to individual variable names to aid + // selecting correct values + double a1 = matrix[0][0]; + double b1 = matrix[0][1]; + double c1 = matrix[0][2]; + double d1 = matrix[0][3]; + + double a2 = matrix[1][0]; + double b2 = matrix[1][1]; + double c2 = matrix[1][2]; + double d2 = matrix[1][3]; + + double a3 = matrix[2][0]; + double b3 = matrix[2][1]; + double c3 = matrix[2][2]; + double d3 = matrix[2][3]; + + double a4 = matrix[3][0]; + double b4 = matrix[3][1]; + double c4 = matrix[3][2]; + double d4 = matrix[3][3]; + + // Row column labeling reversed since we transpose rows & columns + result[0][0] = determinant3x3(b2, b3, b4, c2, c3, c4, d2, d3, d4); + result[1][0] = - determinant3x3(a2, a3, a4, c2, c3, c4, d2, d3, d4); + result[2][0] = determinant3x3(a2, a3, a4, b2, b3, b4, d2, d3, d4); + result[3][0] = - determinant3x3(a2, a3, a4, b2, b3, b4, c2, c3, c4); + + result[0][1] = - determinant3x3(b1, b3, b4, c1, c3, c4, d1, d3, d4); + result[1][1] = determinant3x3(a1, a3, a4, c1, c3, c4, d1, d3, d4); + result[2][1] = - determinant3x3(a1, a3, a4, b1, b3, b4, d1, d3, d4); + result[3][1] = determinant3x3(a1, a3, a4, b1, b3, b4, c1, c3, c4); + + result[0][2] = determinant3x3(b1, b2, b4, c1, c2, c4, d1, d2, d4); + result[1][2] = - determinant3x3(a1, a2, a4, c1, c2, c4, d1, d2, d4); + result[2][2] = determinant3x3(a1, a2, a4, b1, b2, b4, d1, d2, d4); + result[3][2] = - determinant3x3(a1, a2, a4, b1, b2, b4, c1, c2, c4); + + result[0][3] = - determinant3x3(b1, b2, b3, c1, c2, c3, d1, d2, d3); + result[1][3] = determinant3x3(a1, a2, a3, c1, c2, c3, d1, d2, d3); + result[2][3] = - determinant3x3(a1, a2, a3, b1, b2, b3, d1, d2, d3); + result[3][3] = determinant3x3(a1, a2, a3, b1, b2, b3, c1, c2, c3); + } + + // Returns false if the matrix is not invertible + static bool inverse(const TransformationMatrix::Matrix4& matrix, TransformationMatrix::Matrix4& result) + { + // Calculate the adjoint matrix + adjoint(matrix, result); + + // Calculate the 4x4 determinant + // If the determinant is zero, + // then the inverse matrix is not unique. + double det = determinant4x4(matrix); + + if (fabs(det) < SMALL_NUMBER) + return false; + + // Scale the adjoint matrix to get the inverse + + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + result[i][j] = result[i][j] / det; + + return true; + } + + // End of code adapted from Matrix Inversion by Richard Carling + + // Perform a decomposition on the passed matrix, return false if unsuccessful + // From Graphics Gems: unmatrix.c + + // Transpose rotation portion of matrix a, return b + static void transposeMatrix4(const TransformationMatrix::Matrix4& a, TransformationMatrix::Matrix4& b) + { + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + b[i][j] = a[j][i]; + } + + // Multiply a homogeneous point by a matrix and return the transformed point + static void v4MulPointByMatrix(const Vector4 p, const TransformationMatrix::Matrix4& m, Vector4 result) + { + result[0] = (p[0] * m[0][0]) + (p[1] * m[1][0]) + + (p[2] * m[2][0]) + (p[3] * m[3][0]); + result[1] = (p[0] * m[0][1]) + (p[1] * m[1][1]) + + (p[2] * m[2][1]) + (p[3] * m[3][1]); + result[2] = (p[0] * m[0][2]) + (p[1] * m[1][2]) + + (p[2] * m[2][2]) + (p[3] * m[3][2]); + result[3] = (p[0] * m[0][3]) + (p[1] * m[1][3]) + + (p[2] * m[2][3]) + (p[3] * m[3][3]); + } + + static double v3Length(Vector3 a) + { + return sqrt((a[0] * a[0]) + (a[1] * a[1]) + (a[2] * a[2])); + } + + static void v3Scale(Vector3 v, double desiredLength) + { + double len = v3Length(v); + if (len != 0) { + double l = desiredLength / len; + v[0] *= l; + v[1] *= l; + v[2] *= l; + } + } + + static double v3Dot(const Vector3 a, const Vector3 b) + { + return (a[0] * b[0]) + (a[1] * b[1]) + (a[2] * b[2]); + } + + // Make a linear combination of two vectors and return the result. + // result = (a * ascl) + (b * bscl) + static void v3Combine(const Vector3 a, const Vector3 b, Vector3 result, double ascl, double bscl) + { + result[0] = (ascl * a[0]) + (bscl * b[0]); + result[1] = (ascl * a[1]) + (bscl * b[1]); + result[2] = (ascl * a[2]) + (bscl * b[2]); + } + + // Return the cross product result = a cross b */ + static void v3Cross(const Vector3 a, const Vector3 b, Vector3 result) + { + result[0] = (a[1] * b[2]) - (a[2] * b[1]); + result[1] = (a[2] * b[0]) - (a[0] * b[2]); + result[2] = (a[0] * b[1]) - (a[1] * b[0]); + } + + static bool decompose(const TransformationMatrix::Matrix4& mat, TransformationMatrix::DecomposedType& result) + { + TransformationMatrix::Matrix4 localMatrix; + memcpy(localMatrix, mat, sizeof(TransformationMatrix::Matrix4)); + + // Normalize the matrix. + if (localMatrix[3][3] == 0) + return false; + + int i, j; + for (i = 0; i < 4; i++) + for (j = 0; j < 4; j++) + localMatrix[i][j] /= localMatrix[3][3]; + + // perspectiveMatrix is used to solve for perspective, but it also provides + // an easy way to test for singularity of the upper 3x3 component. + TransformationMatrix::Matrix4 perspectiveMatrix; + memcpy(perspectiveMatrix, localMatrix, sizeof(TransformationMatrix::Matrix4)); + for (i = 0; i < 3; i++) + perspectiveMatrix[i][3] = 0; + perspectiveMatrix[3][3] = 1; + + if (determinant4x4(perspectiveMatrix) == 0) + return false; + + // First, isolate perspective. This is the messiest. + if (localMatrix[0][3] != 0 || localMatrix[1][3] != 0 || localMatrix[2][3] != 0) { + // rightHandSide is the right hand side of the equation. + Vector4 rightHandSide; + rightHandSide[0] = localMatrix[0][3]; + rightHandSide[1] = localMatrix[1][3]; + rightHandSide[2] = localMatrix[2][3]; + rightHandSide[3] = localMatrix[3][3]; + + // Solve the equation by inverting perspectiveMatrix and multiplying + // rightHandSide by the inverse. (This is the easiest way, not + // necessarily the best.) + TransformationMatrix::Matrix4 inversePerspectiveMatrix, transposedInversePerspectiveMatrix; + inverse(perspectiveMatrix, inversePerspectiveMatrix); + transposeMatrix4(inversePerspectiveMatrix, transposedInversePerspectiveMatrix); + + Vector4 perspectivePoint; + v4MulPointByMatrix(rightHandSide, transposedInversePerspectiveMatrix, perspectivePoint); + + result.perspectiveX = perspectivePoint[0]; + result.perspectiveY = perspectivePoint[1]; + result.perspectiveZ = perspectivePoint[2]; + result.perspectiveW = perspectivePoint[3]; + + // Clear the perspective partition + localMatrix[0][3] = localMatrix[1][3] = localMatrix[2][3] = 0; + localMatrix[3][3] = 1; + } else { + // No perspective. + result.perspectiveX = result.perspectiveY = result.perspectiveZ = 0; + result.perspectiveW = 1; + } + + // Next take care of translation (easy). + result.translateX = localMatrix[3][0]; + localMatrix[3][0] = 0; + result.translateY = localMatrix[3][1]; + localMatrix[3][1] = 0; + result.translateZ = localMatrix[3][2]; + localMatrix[3][2] = 0; + + // Vector4 type and functions need to be added to the common set. + Vector3 row[3], pdum3; + + // Now get scale and shear. + for (i = 0; i < 3; i++) { + row[i][0] = localMatrix[i][0]; + row[i][1] = localMatrix[i][1]; + row[i][2] = localMatrix[i][2]; + } + + // Compute X scale factor and normalize first row. + result.scaleX = v3Length(row[0]); + v3Scale(row[0], 1.0); + + // Compute XY shear factor and make 2nd row orthogonal to 1st. + result.skewXY = v3Dot(row[0], row[1]); + v3Combine(row[1], row[0], row[1], 1.0, -result.skewXY); + + // Now, compute Y scale and normalize 2nd row. + result.scaleY = v3Length(row[1]); + v3Scale(row[1], 1.0); + result.skewXY /= result.scaleY; + + // Compute XZ and YZ shears, orthogonalize 3rd row. + result.skewXZ = v3Dot(row[0], row[2]); + v3Combine(row[2], row[0], row[2], 1.0, -result.skewXZ); + result.skewYZ = v3Dot(row[1], row[2]); + v3Combine(row[2], row[1], row[2], 1.0, -result.skewYZ); + + // Next, get Z scale and normalize 3rd row. + result.scaleZ = v3Length(row[2]); + v3Scale(row[2], 1.0); + result.skewXZ /= result.scaleZ; + result.skewYZ /= result.scaleZ; + + // At this point, the matrix (in rows[]) is orthonormal. + // Check for a coordinate system flip. If the determinant + // is -1, then negate the matrix and the scaling factors. + v3Cross(row[1], row[2], pdum3); + if (v3Dot(row[0], pdum3) < 0) { + + result.scaleX *= -1; + result.scaleY *= -1; + result.scaleZ *= -1; + + for (i = 0; i < 3; i++) { + row[i][0] *= -1; + row[i][1] *= -1; + row[i][2] *= -1; + } + } + + // Now, get the rotations out, as described in the gem. + + result.rotateY = asin(-row[0][2]); + if (cos(result.rotateY) != 0) { + result.rotateX = atan2(row[1][2], row[2][2]); + result.rotateZ = atan2(row[0][1], row[0][0]); + } else { + result.rotateX = atan2(-row[2][0], row[1][1]); + result.rotateZ = 0; + } + + double s, t, x, y, z, w; + + t = row[0][0] + row[1][1] + row[2][2] + 1.0; + + if (t > 1e-4) { + s = 0.5 / sqrt(t); + w = 0.25 / s; + x = (row[2][1] - row[1][2]) * s; + y = (row[0][2] - row[2][0]) * s; + z = (row[1][0] - row[0][1]) * s; + } else if (row[0][0] > row[1][1] && row[0][0] > row[2][2]) { + s = sqrt (1.0 + row[0][0] - row[1][1] - row[2][2]) * 2.0; // S=4*qx + x = 0.25 * s; + y = (row[0][1] + row[1][0]) / s; + z = (row[0][2] + row[2][0]) / s; + w = (row[2][1] - row[1][2]) / s; + } else if (row[1][1] > row[2][2]) { + s = sqrt (1.0 + row[1][1] - row[0][0] - row[2][2]) * 2.0; // S=4*qy + x = (row[0][1] + row[1][0]) / s; + y = 0.25 * s; + z = (row[1][2] + row[2][1]) / s; + w = (row[0][2] - row[2][0]) / s; + } else { + s = sqrt(1.0 + row[2][2] - row[0][0] - row[1][1]) * 2.0; // S=4*qz + x = (row[0][2] + row[2][0]) / s; + y = (row[1][2] + row[2][1]) / s; + z = 0.25 * s; + w = (row[1][0] - row[0][1]) / s; + } + + result.quaternionX = x; + result.quaternionY = y; + result.quaternionZ = z; + result.quaternionW = w; + + return true; + } + + // Perform a spherical linear interpolation between the two + // passed quaternions with 0 <= t <= 1 + static void slerp(double qa[4], const double qb[4], double t) + { + double ax, ay, az, aw; + double bx, by, bz, bw; + double cx, cy, cz, cw; + double angle; + double th, invth, scale, invscale; + + ax = qa[0]; ay = qa[1]; az = qa[2]; aw = qa[3]; + bx = qb[0]; by = qb[1]; bz = qb[2]; bw = qb[3]; + + angle = ax * bx + ay * by + az * bz + aw * bw; + + if (angle < 0.0) { + ax = -ax; ay = -ay; + az = -az; aw = -aw; + angle = -angle; + } + + if (angle + 1.0 > .05) { + if (1.0 - angle >= .05) { + th = acos (angle); + invth = 1.0 / sin (th); + scale = sin (th * (1.0 - t)) * invth; + invscale = sin (th * t) * invth; + } else { + scale = 1.0 - t; + invscale = t; + } + } else { + bx = -ay; + by = ax; + bz = -aw; + bw = az; + scale = sin(M_PI * (.5 - t)); + invscale = sin (M_PI * t); + } + + cx = ax * scale + bx * invscale; + cy = ay * scale + by * invscale; + cz = az * scale + bz * invscale; + cw = aw * scale + bw * invscale; + + qa[0] = cx; qa[1] = cy; qa[2] = cz; qa[3] = cw; + } + + // End of Supporting Math Functions + + TransformationMatrix::TransformationMatrix(const CGAffineTransform& t) + { + setMatrix(t.a, t.b, t.c, t.d, t.tx, t.ty); + } + + TransformationMatrix::TransformationMatrix(const CATransform3D& t) + { + setMatrix( + t.m11, t.m12, t.m13, t.m14, + t.m21, t.m22, t.m23, t.m24, + t.m31, t.m32, t.m33, t.m34, + t.m41, t.m42, t.m43, t.m44); + } + + CATransform3D TransformationMatrix::transform3d() const + { + CATransform3D t; + t.m11 = narrowPrecisionToFloat(m11()); + t.m12 = narrowPrecisionToFloat(m12()); + t.m13 = narrowPrecisionToFloat(m13()); + t.m14 = narrowPrecisionToFloat(m14()); + t.m21 = narrowPrecisionToFloat(m21()); + t.m22 = narrowPrecisionToFloat(m22()); + t.m23 = narrowPrecisionToFloat(m23()); + t.m24 = narrowPrecisionToFloat(m24()); + t.m31 = narrowPrecisionToFloat(m31()); + t.m32 = narrowPrecisionToFloat(m32()); + t.m33 = narrowPrecisionToFloat(m33()); + t.m34 = narrowPrecisionToFloat(m34()); + t.m41 = narrowPrecisionToFloat(m41()); + t.m42 = narrowPrecisionToFloat(m42()); + t.m43 = narrowPrecisionToFloat(m43()); + t.m44 = narrowPrecisionToFloat(m44()); + return t; + } + + CGAffineTransform TransformationMatrix::affineTransform () const + { + CGAffineTransform t; + t.a = narrowPrecisionToFloat(m11()); + t.b = narrowPrecisionToFloat(m12()); + t.c = narrowPrecisionToFloat(m21()); + t.d = narrowPrecisionToFloat(m22()); + t.tx = narrowPrecisionToFloat(m41()); + t.ty = narrowPrecisionToFloat(m42()); + return t; + } + + TransformationMatrix::operator CATransform3D() const + { + return transform3d(); + } + + TransformationMatrix& TransformationMatrix::scale(double s) + { + return scaleNonUniform(s, s); + } + + TransformationMatrix& TransformationMatrix::rotateFromVector(double x, double y) + { + return rotate(rad2deg(atan2(y, x))); + } + + TransformationMatrix& TransformationMatrix::flipX() + { + return scaleNonUniform(-1.0, 1.0); + } + + TransformationMatrix& TransformationMatrix::flipY() + { + return scaleNonUniform(1.0, -1.0); + } + + TransformationMatrix& TransformationMatrix::scaleNonUniform(double sx, double sy) + { + m_matrix[0][0] *= sx; + m_matrix[0][1] *= sx; + m_matrix[0][2] *= sx; + m_matrix[0][3] *= sx; + + m_matrix[1][0] *= sy; + m_matrix[1][1] *= sy; + m_matrix[1][2] *= sy; + m_matrix[1][3] *= sy; + return *this; + } + + TransformationMatrix& TransformationMatrix::scale3d(double sx, double sy, double sz) + { + scaleNonUniform(sx, sy); + + m_matrix[2][0] *= sz; + m_matrix[2][1] *= sz; + m_matrix[2][2] *= sz; + m_matrix[2][3] *= sz; + return *this; + } + + TransformationMatrix& TransformationMatrix::rotate3d(double x, double y, double z, double angle) + { + // Normalize the axis of rotation + double length = sqrt(x * x + y * y + z * z); + if (length == 0) { + // A direction vector that cannot be normalized, such as [0, 0, 0], will cause the rotation to not be applied. + return *this; + } else if (length != 1) { + x /= length; + y /= length; + z /= length; + } + + // Angles are in degrees. Switch to radians. + angle = deg2rad(angle); + + double sinTheta = sin(angle); + double cosTheta = cos(angle); + + TransformationMatrix mat; + + // Optimize cases where the axis is along a major axis + if (x == 1.0 && y == 0.0 && z == 0.0) { + mat.m_matrix[0][0] = 1.0; + mat.m_matrix[0][1] = 0.0; + mat.m_matrix[0][2] = 0.0; + mat.m_matrix[1][0] = 0.0; + mat.m_matrix[1][1] = cosTheta; + mat.m_matrix[1][2] = sinTheta; + mat.m_matrix[2][0] = 0.0; + mat.m_matrix[2][1] = -sinTheta; + mat.m_matrix[2][2] = cosTheta; + mat.m_matrix[0][3] = mat.m_matrix[1][3] = mat.m_matrix[2][3] = 0.0; + mat.m_matrix[3][0] = mat.m_matrix[3][1] = mat.m_matrix[3][2] = 0.0; + mat.m_matrix[3][3] = 1.0; + } else if (x == 0.0 && y == 1.0 && z == 0.0) { + mat.m_matrix[0][0] = cosTheta; + mat.m_matrix[0][1] = 0.0; + mat.m_matrix[0][2] = -sinTheta; + mat.m_matrix[1][0] = 0.0; + mat.m_matrix[1][1] = 1.0; + mat.m_matrix[1][2] = 0.0; + mat.m_matrix[2][0] = sinTheta; + mat.m_matrix[2][1] = 0.0; + mat.m_matrix[2][2] = cosTheta; + mat.m_matrix[0][3] = mat.m_matrix[1][3] = mat.m_matrix[2][3] = 0.0; + mat.m_matrix[3][0] = mat.m_matrix[3][1] = mat.m_matrix[3][2] = 0.0; + mat.m_matrix[3][3] = 1.0; + } else if (x == 0.0 && y == 0.0 && z == 1.0) { + mat.m_matrix[0][0] = cosTheta; + mat.m_matrix[0][1] = sinTheta; + mat.m_matrix[0][2] = 0.0; + mat.m_matrix[1][0] = -sinTheta; + mat.m_matrix[1][1] = cosTheta; + mat.m_matrix[1][2] = 0.0; + mat.m_matrix[2][0] = 0.0; + mat.m_matrix[2][1] = 0.0; + mat.m_matrix[2][2] = 1.0; + mat.m_matrix[0][3] = mat.m_matrix[1][3] = mat.m_matrix[2][3] = 0.0; + mat.m_matrix[3][0] = mat.m_matrix[3][1] = mat.m_matrix[3][2] = 0.0; + mat.m_matrix[3][3] = 1.0; + } else { + // This case is the rotation about an arbitrary unit vector. + // + // Formula is adapted from Wikipedia article on Rotation matrix, + // http://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle + // + // An alternate resource with the same matrix: http://www.fastgraph.com/makegames/3drotation/ + // + double oneMinusCosTheta = 1 - cosTheta; + mat.m_matrix[0][0] = cosTheta + x * x * oneMinusCosTheta; + mat.m_matrix[0][1] = y * x * oneMinusCosTheta + z * sinTheta; + mat.m_matrix[0][2] = z * x * oneMinusCosTheta - y * sinTheta; + mat.m_matrix[1][0] = x * y * oneMinusCosTheta - z * sinTheta; + mat.m_matrix[1][1] = cosTheta + y * y * oneMinusCosTheta; + mat.m_matrix[1][2] = z * y * oneMinusCosTheta + x * sinTheta; + mat.m_matrix[2][0] = x * z * oneMinusCosTheta + y * sinTheta; + mat.m_matrix[2][1] = y * z * oneMinusCosTheta - x * sinTheta; + mat.m_matrix[2][2] = cosTheta + z * z * oneMinusCosTheta; + mat.m_matrix[0][3] = mat.m_matrix[1][3] = mat.m_matrix[2][3] = 0.0; + mat.m_matrix[3][0] = mat.m_matrix[3][1] = mat.m_matrix[3][2] = 0.0; + mat.m_matrix[3][3] = 1.0; + } + multiply(mat); + return *this; + } + + TransformationMatrix& TransformationMatrix::rotate3d(double rx, double ry, double rz) + { + // Angles are in degrees. Switch to radians. + rx = deg2rad(rx); + ry = deg2rad(ry); + rz = deg2rad(rz); + + TransformationMatrix mat; + + double sinTheta = sin(rz); + double cosTheta = cos(rz); + + mat.m_matrix[0][0] = cosTheta; + mat.m_matrix[0][1] = sinTheta; + mat.m_matrix[0][2] = 0.0; + mat.m_matrix[1][0] = -sinTheta; + mat.m_matrix[1][1] = cosTheta; + mat.m_matrix[1][2] = 0.0; + mat.m_matrix[2][0] = 0.0; + mat.m_matrix[2][1] = 0.0; + mat.m_matrix[2][2] = 1.0; + mat.m_matrix[0][3] = mat.m_matrix[1][3] = mat.m_matrix[2][3] = 0.0; + mat.m_matrix[3][0] = mat.m_matrix[3][1] = mat.m_matrix[3][2] = 0.0; + mat.m_matrix[3][3] = 1.0; + + TransformationMatrix rmat(mat); + + sinTheta = sin(ry); + cosTheta = cos(ry); + + mat.m_matrix[0][0] = cosTheta; + mat.m_matrix[0][1] = 0.0; + mat.m_matrix[0][2] = -sinTheta; + mat.m_matrix[1][0] = 0.0; + mat.m_matrix[1][1] = 1.0; + mat.m_matrix[1][2] = 0.0; + mat.m_matrix[2][0] = sinTheta; + mat.m_matrix[2][1] = 0.0; + mat.m_matrix[2][2] = cosTheta; + mat.m_matrix[0][3] = mat.m_matrix[1][3] = mat.m_matrix[2][3] = 0.0; + mat.m_matrix[3][0] = mat.m_matrix[3][1] = mat.m_matrix[3][2] = 0.0; + mat.m_matrix[3][3] = 1.0; + + rmat.multiply(mat); + + sinTheta = sin(rx); + cosTheta = cos(rx); + + mat.m_matrix[0][0] = 1.0; + mat.m_matrix[0][1] = 0.0; + mat.m_matrix[0][2] = 0.0; + mat.m_matrix[1][0] = 0.0; + mat.m_matrix[1][1] = cosTheta; + mat.m_matrix[1][2] = sinTheta; + mat.m_matrix[2][0] = 0.0; + mat.m_matrix[2][1] = -sinTheta; + mat.m_matrix[2][2] = cosTheta; + mat.m_matrix[0][3] = mat.m_matrix[1][3] = mat.m_matrix[2][3] = 0.0; + mat.m_matrix[3][0] = mat.m_matrix[3][1] = mat.m_matrix[3][2] = 0.0; + mat.m_matrix[3][3] = 1.0; + + rmat.multiply(mat); + + multiply(rmat); + return *this; + } + + TransformationMatrix& TransformationMatrix::translate(double tx, double ty) + { + m_matrix[3][0] += tx * m_matrix[0][0] + ty * m_matrix[1][0]; + m_matrix[3][1] += tx * m_matrix[0][1] + ty * m_matrix[1][1]; + m_matrix[3][2] += tx * m_matrix[0][2] + ty * m_matrix[1][2]; + m_matrix[3][3] += tx * m_matrix[0][3] + ty * m_matrix[1][3]; + return *this; + } + + TransformationMatrix& TransformationMatrix::translate3d(double tx, double ty, double tz) + { + m_matrix[3][0] += tx * m_matrix[0][0] + ty * m_matrix[1][0] + tz * m_matrix[2][0]; + m_matrix[3][1] += tx * m_matrix[0][1] + ty * m_matrix[1][1] + tz * m_matrix[2][1]; + m_matrix[3][2] += tx * m_matrix[0][2] + ty * m_matrix[1][2] + tz * m_matrix[2][2]; + m_matrix[3][3] += tx * m_matrix[0][3] + ty * m_matrix[1][3] + tz * m_matrix[2][3]; + return *this; + } + + TransformationMatrix& TransformationMatrix::translateRight(double tx, double ty) + { + if (tx != 0) { + m_matrix[0][0] += m_matrix[0][3] * tx; + m_matrix[1][0] += m_matrix[1][3] * tx; + m_matrix[2][0] += m_matrix[2][3] * tx; + m_matrix[3][0] += m_matrix[3][3] * tx; + } + + if (ty != 0) { + m_matrix[0][1] += m_matrix[0][3] * ty; + m_matrix[1][1] += m_matrix[1][3] * ty; + m_matrix[2][1] += m_matrix[2][3] * ty; + m_matrix[3][1] += m_matrix[3][3] * ty; + } + + return *this; + } + + TransformationMatrix& TransformationMatrix::translateRight3d(double tx, double ty, double tz) + { + translateRight(tx, ty); + if (tz != 0) { + m_matrix[0][2] += m_matrix[0][3] * tz; + m_matrix[1][2] += m_matrix[1][3] * tz; + m_matrix[2][2] += m_matrix[2][3] * tz; + m_matrix[3][2] += m_matrix[3][3] * tz; + } + + return *this; + } + + TransformationMatrix& TransformationMatrix::skew(double sx, double sy) + { + // angles are in degrees. Switch to radians + sx = deg2rad(sx); + sy = deg2rad(sy); + + TransformationMatrix mat; + mat.m_matrix[0][1] = tan(sy); // note that the y shear goes in the first row + mat.m_matrix[1][0] = tan(sx); // and the x shear in the second row + + multiply(mat); + return *this; + } + + TransformationMatrix& TransformationMatrix::applyPerspective(double p) + { + TransformationMatrix mat; + if (p != 0) + mat.m_matrix[2][3] = -1/p; + + multiply(mat); + return *this; + } + + // this = mat * this. + TransformationMatrix& TransformationMatrix::multiply(const TransformationMatrix& mat) + { + Matrix4 tmp; + + tmp[0][0] = (mat.m_matrix[0][0] * m_matrix[0][0] + mat.m_matrix[0][1] * m_matrix[1][0] + + mat.m_matrix[0][2] * m_matrix[2][0] + mat.m_matrix[0][3] * m_matrix[3][0]); + tmp[0][1] = (mat.m_matrix[0][0] * m_matrix[0][1] + mat.m_matrix[0][1] * m_matrix[1][1] + + mat.m_matrix[0][2] * m_matrix[2][1] + mat.m_matrix[0][3] * m_matrix[3][1]); + tmp[0][2] = (mat.m_matrix[0][0] * m_matrix[0][2] + mat.m_matrix[0][1] * m_matrix[1][2] + + mat.m_matrix[0][2] * m_matrix[2][2] + mat.m_matrix[0][3] * m_matrix[3][2]); + tmp[0][3] = (mat.m_matrix[0][0] * m_matrix[0][3] + mat.m_matrix[0][1] * m_matrix[1][3] + + mat.m_matrix[0][2] * m_matrix[2][3] + mat.m_matrix[0][3] * m_matrix[3][3]); + + tmp[1][0] = (mat.m_matrix[1][0] * m_matrix[0][0] + mat.m_matrix[1][1] * m_matrix[1][0] + + mat.m_matrix[1][2] * m_matrix[2][0] + mat.m_matrix[1][3] * m_matrix[3][0]); + tmp[1][1] = (mat.m_matrix[1][0] * m_matrix[0][1] + mat.m_matrix[1][1] * m_matrix[1][1] + + mat.m_matrix[1][2] * m_matrix[2][1] + mat.m_matrix[1][3] * m_matrix[3][1]); + tmp[1][2] = (mat.m_matrix[1][0] * m_matrix[0][2] + mat.m_matrix[1][1] * m_matrix[1][2] + + mat.m_matrix[1][2] * m_matrix[2][2] + mat.m_matrix[1][3] * m_matrix[3][2]); + tmp[1][3] = (mat.m_matrix[1][0] * m_matrix[0][3] + mat.m_matrix[1][1] * m_matrix[1][3] + + mat.m_matrix[1][2] * m_matrix[2][3] + mat.m_matrix[1][3] * m_matrix[3][3]); + + tmp[2][0] = (mat.m_matrix[2][0] * m_matrix[0][0] + mat.m_matrix[2][1] * m_matrix[1][0] + + mat.m_matrix[2][2] * m_matrix[2][0] + mat.m_matrix[2][3] * m_matrix[3][0]); + tmp[2][1] = (mat.m_matrix[2][0] * m_matrix[0][1] + mat.m_matrix[2][1] * m_matrix[1][1] + + mat.m_matrix[2][2] * m_matrix[2][1] + mat.m_matrix[2][3] * m_matrix[3][1]); + tmp[2][2] = (mat.m_matrix[2][0] * m_matrix[0][2] + mat.m_matrix[2][1] * m_matrix[1][2] + + mat.m_matrix[2][2] * m_matrix[2][2] + mat.m_matrix[2][3] * m_matrix[3][2]); + tmp[2][3] = (mat.m_matrix[2][0] * m_matrix[0][3] + mat.m_matrix[2][1] * m_matrix[1][3] + + mat.m_matrix[2][2] * m_matrix[2][3] + mat.m_matrix[2][3] * m_matrix[3][3]); + + tmp[3][0] = (mat.m_matrix[3][0] * m_matrix[0][0] + mat.m_matrix[3][1] * m_matrix[1][0] + + mat.m_matrix[3][2] * m_matrix[2][0] + mat.m_matrix[3][3] * m_matrix[3][0]); + tmp[3][1] = (mat.m_matrix[3][0] * m_matrix[0][1] + mat.m_matrix[3][1] * m_matrix[1][1] + + mat.m_matrix[3][2] * m_matrix[2][1] + mat.m_matrix[3][3] * m_matrix[3][1]); + tmp[3][2] = (mat.m_matrix[3][0] * m_matrix[0][2] + mat.m_matrix[3][1] * m_matrix[1][2] + + mat.m_matrix[3][2] * m_matrix[2][2] + mat.m_matrix[3][3] * m_matrix[3][2]); + tmp[3][3] = (mat.m_matrix[3][0] * m_matrix[0][3] + mat.m_matrix[3][1] * m_matrix[1][3] + + mat.m_matrix[3][2] * m_matrix[2][3] + mat.m_matrix[3][3] * m_matrix[3][3]); + + setMatrix(tmp); + return *this; + } + + void TransformationMatrix::multVecMatrix(double x, double y, double& resultX, double& resultY) const + { + resultX = m_matrix[3][0] + x * m_matrix[0][0] + y * m_matrix[1][0]; + resultY = m_matrix[3][1] + x * m_matrix[0][1] + y * m_matrix[1][1]; + double w = m_matrix[3][3] + x * m_matrix[0][3] + y * m_matrix[1][3]; + if (w != 1 && w != 0) { + resultX /= w; + resultY /= w; + } + } + + void TransformationMatrix::multVecMatrix(double x, double y, double z, double& resultX, double& resultY, double& resultZ) const + { + resultX = m_matrix[3][0] + x * m_matrix[0][0] + y * m_matrix[1][0] + z * m_matrix[2][0]; + resultY = m_matrix[3][1] + x * m_matrix[0][1] + y * m_matrix[1][1] + z * m_matrix[2][1]; + resultZ = m_matrix[3][2] + x * m_matrix[0][2] + y * m_matrix[1][2] + z * m_matrix[2][2]; + double w = m_matrix[3][3] + x * m_matrix[0][3] + y * m_matrix[1][3] + z * m_matrix[2][3]; + if (w != 1 && w != 0) { + resultX /= w; + resultY /= w; + resultZ /= w; + } + } + + bool TransformationMatrix::isInvertible() const + { + if (isIdentityOrTranslation()) + return true; + + double det = WebCore::determinant4x4(m_matrix); + + if (fabs(det) < SMALL_NUMBER) + return false; + + return true; + } + + TransformationMatrix TransformationMatrix::inverse() const + { + if (isIdentityOrTranslation()) { + // identity matrix + if (m_matrix[3][0] == 0 && m_matrix[3][1] == 0 && m_matrix[3][2] == 0) + return TransformationMatrix(); + + // translation + return TransformationMatrix(1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + -m_matrix[3][0], -m_matrix[3][1], -m_matrix[3][2], 1); + } + + TransformationMatrix invMat; + bool inverted = WebCore::inverse(m_matrix, invMat.m_matrix); + if (!inverted) + return TransformationMatrix(); + + return invMat; + } + + void TransformationMatrix::makeAffine() + { + m_matrix[0][2] = 0; + m_matrix[0][3] = 0; + + m_matrix[1][2] = 0; + m_matrix[1][3] = 0; + + m_matrix[2][0] = 0; + m_matrix[2][1] = 0; + m_matrix[2][2] = 1; + m_matrix[2][3] = 0; + + m_matrix[3][2] = 0; + m_matrix[3][3] = 1; + } + + static inline void blendFloat(double& from, double to, double progress) + { + if (from != to) + from = from + (to - from) * progress; + } + + void TransformationMatrix::blend(const TransformationMatrix& from, double progress) + { + if (from.isIdentity() && isIdentity()) + return; + + // decompose + DecomposedType fromDecomp; + DecomposedType toDecomp; + from.decompose(fromDecomp); + decompose(toDecomp); + + // interpolate + blendFloat(fromDecomp.scaleX, toDecomp.scaleX, progress); + blendFloat(fromDecomp.scaleY, toDecomp.scaleY, progress); + blendFloat(fromDecomp.scaleZ, toDecomp.scaleZ, progress); + blendFloat(fromDecomp.skewXY, toDecomp.skewXY, progress); + blendFloat(fromDecomp.skewXZ, toDecomp.skewXZ, progress); + blendFloat(fromDecomp.skewYZ, toDecomp.skewYZ, progress); + blendFloat(fromDecomp.translateX, toDecomp.translateX, progress); + blendFloat(fromDecomp.translateY, toDecomp.translateY, progress); + blendFloat(fromDecomp.translateZ, toDecomp.translateZ, progress); + blendFloat(fromDecomp.perspectiveX, toDecomp.perspectiveX, progress); + blendFloat(fromDecomp.perspectiveY, toDecomp.perspectiveY, progress); + blendFloat(fromDecomp.perspectiveZ, toDecomp.perspectiveZ, progress); + blendFloat(fromDecomp.perspectiveW, toDecomp.perspectiveW, progress); + + slerp(&fromDecomp.quaternionX, &toDecomp.quaternionX, progress); + + // recompose + recompose(fromDecomp); + } + + bool TransformationMatrix::decompose(DecomposedType& decomp) const + { + if (isIdentity()) { + memset(&decomp, 0, sizeof(decomp)); + decomp.perspectiveW = 1; + decomp.scaleX = 1; + decomp.scaleY = 1; + decomp.scaleZ = 1; + } + + if (!WebCore::decompose(m_matrix, decomp)) + return false; + return true; + } + + void TransformationMatrix::recompose(const DecomposedType& decomp, bool useEulerAngle) + { + makeIdentity(); + + // first apply perspective + m_matrix[0][3] = decomp.perspectiveX; + m_matrix[1][3] = decomp.perspectiveY; + m_matrix[2][3] = decomp.perspectiveZ; + m_matrix[3][3] = decomp.perspectiveW; + + // now translate + translate3d(decomp.translateX, decomp.translateY, decomp.translateZ); + + if (!useEulerAngle) { + // apply rotation + double xx = decomp.quaternionX * decomp.quaternionX; + double xy = decomp.quaternionX * decomp.quaternionY; + double xz = decomp.quaternionX * decomp.quaternionZ; + double xw = decomp.quaternionX * decomp.quaternionW; + double yy = decomp.quaternionY * decomp.quaternionY; + double yz = decomp.quaternionY * decomp.quaternionZ; + double yw = decomp.quaternionY * decomp.quaternionW; + double zz = decomp.quaternionZ * decomp.quaternionZ; + double zw = decomp.quaternionZ * decomp.quaternionW; + + // Construct a composite rotation matrix from the quaternion values + TransformationMatrix rotationMatrix(1 - 2 * (yy + zz), 2 * (xy - zw), 2 * (xz + yw), 0, + 2 * (xy + zw), 1 - 2 * (xx + zz), 2 * (yz - xw), 0, + 2 * (xz - yw), 2 * (yz + xw), 1 - 2 * (xx + yy), 0, + 0, 0, 0, 1); + + multiply(rotationMatrix); + } else { + rotate3d(1.0, 0.0, 0.0, rad2deg(decomp.rotateX)); + rotate3d(0.0, 1.0, 0.0, rad2deg(decomp.rotateY)); + rotate3d(0.0, 0.0, 1.0, rad2deg(decomp.rotateZ)); + } + + // now apply skew + if (decomp.skewYZ) { + TransformationMatrix tmp; + tmp.setM32(decomp.skewYZ); + multiply(tmp); + } + + if (decomp.skewXZ) { + TransformationMatrix tmp; + tmp.setM31(decomp.skewXZ); + multiply(tmp); + } + + if (decomp.skewXY) { + TransformationMatrix tmp; + tmp.setM21(decomp.skewXY); + multiply(tmp); + } + + // finally, apply scale + scale3d(decomp.scaleX, decomp.scaleY, decomp.scaleZ); + } +} \ No newline at end of file diff --git a/pop/WebCore/TransformationMatrix.h b/pop/WebCore/TransformationMatrix.h new file mode 100644 index 00000000..9dad2e9b --- /dev/null +++ b/pop/WebCore/TransformationMatrix.h @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2005, 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TransformationMatrix_h +#define TransformationMatrix_h + +#include //for memcpy +#include +#include + +namespace WebCore { + + class TransformationMatrix { + public: + + typedef double Matrix4[4][4]; + + TransformationMatrix() { makeIdentity(); } + TransformationMatrix(const TransformationMatrix& t) { *this = t; } + TransformationMatrix(double a, double b, double c, double d, double e, double f) { setMatrix(a, b, c, d, e, f); } + TransformationMatrix(double m11, double m12, double m13, double m14, + double m21, double m22, double m23, double m24, + double m31, double m32, double m33, double m34, + double m41, double m42, double m43, double m44) + { + setMatrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); + } + + void setMatrix(double a, double b, double c, double d, double e, double f) + { + m_matrix[0][0] = a; m_matrix[0][1] = b; m_matrix[0][2] = 0; m_matrix[0][3] = 0; + m_matrix[1][0] = c; m_matrix[1][1] = d; m_matrix[1][2] = 0; m_matrix[1][3] = 0; + m_matrix[2][0] = 0; m_matrix[2][1] = 0; m_matrix[2][2] = 1; m_matrix[2][3] = 0; + m_matrix[3][0] = e; m_matrix[3][1] = f; m_matrix[3][2] = 0; m_matrix[3][3] = 1; + } + + void setMatrix(double m11, double m12, double m13, double m14, + double m21, double m22, double m23, double m24, + double m31, double m32, double m33, double m34, + double m41, double m42, double m43, double m44) + { + m_matrix[0][0] = m11; m_matrix[0][1] = m12; m_matrix[0][2] = m13; m_matrix[0][3] = m14; + m_matrix[1][0] = m21; m_matrix[1][1] = m22; m_matrix[1][2] = m23; m_matrix[1][3] = m24; + m_matrix[2][0] = m31; m_matrix[2][1] = m32; m_matrix[2][2] = m33; m_matrix[2][3] = m34; + m_matrix[3][0] = m41; m_matrix[3][1] = m42; m_matrix[3][2] = m43; m_matrix[3][3] = m44; + } + + TransformationMatrix& operator =(const TransformationMatrix &t) + { + setMatrix(t.m_matrix); + return *this; + } + + TransformationMatrix& makeIdentity() + { + setMatrix(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + return *this; + } + + bool isIdentity() const + { + return m_matrix[0][0] == 1 && m_matrix[0][1] == 0 && m_matrix[0][2] == 0 && m_matrix[0][3] == 0 && + m_matrix[1][0] == 0 && m_matrix[1][1] == 1 && m_matrix[1][2] == 0 && m_matrix[1][3] == 0 && + m_matrix[2][0] == 0 && m_matrix[2][1] == 0 && m_matrix[2][2] == 1 && m_matrix[2][3] == 0 && + m_matrix[3][0] == 0 && m_matrix[3][1] == 0 && m_matrix[3][2] == 0 && m_matrix[3][3] == 1; + } + + // This form preserves the double math from input to output + void map(double x, double y, double& x2, double& y2) const { multVecMatrix(x, y, x2, y2); } + + double m11() const { return m_matrix[0][0]; } + void setM11(double f) { m_matrix[0][0] = f; } + double m12() const { return m_matrix[0][1]; } + void setM12(double f) { m_matrix[0][1] = f; } + double m13() const { return m_matrix[0][2]; } + void setM13(double f) { m_matrix[0][2] = f; } + double m14() const { return m_matrix[0][3]; } + void setM14(double f) { m_matrix[0][3] = f; } + double m21() const { return m_matrix[1][0]; } + void setM21(double f) { m_matrix[1][0] = f; } + double m22() const { return m_matrix[1][1]; } + void setM22(double f) { m_matrix[1][1] = f; } + double m23() const { return m_matrix[1][2]; } + void setM23(double f) { m_matrix[1][2] = f; } + double m24() const { return m_matrix[1][3]; } + void setM24(double f) { m_matrix[1][3] = f; } + double m31() const { return m_matrix[2][0]; } + void setM31(double f) { m_matrix[2][0] = f; } + double m32() const { return m_matrix[2][1]; } + void setM32(double f) { m_matrix[2][1] = f; } + double m33() const { return m_matrix[2][2]; } + void setM33(double f) { m_matrix[2][2] = f; } + double m34() const { return m_matrix[2][3]; } + void setM34(double f) { m_matrix[2][3] = f; } + double m41() const { return m_matrix[3][0]; } + void setM41(double f) { m_matrix[3][0] = f; } + double m42() const { return m_matrix[3][1]; } + void setM42(double f) { m_matrix[3][1] = f; } + double m43() const { return m_matrix[3][2]; } + void setM43(double f) { m_matrix[3][2] = f; } + double m44() const { return m_matrix[3][3]; } + void setM44(double f) { m_matrix[3][3] = f; } + + double a() const { return m_matrix[0][0]; } + void setA(double a) { m_matrix[0][0] = a; } + + double b() const { return m_matrix[0][1]; } + void setB(double b) { m_matrix[0][1] = b; } + + double c() const { return m_matrix[1][0]; } + void setC(double c) { m_matrix[1][0] = c; } + + double d() const { return m_matrix[1][1]; } + void setD(double d) { m_matrix[1][1] = d; } + + double e() const { return m_matrix[3][0]; } + void setE(double e) { m_matrix[3][0] = e; } + + double f() const { return m_matrix[3][1]; } + void setF(double f) { m_matrix[3][1] = f; } + + // this = this * mat + TransformationMatrix& multiply(const TransformationMatrix&); + + TransformationMatrix& scale(double); + TransformationMatrix& scaleNonUniform(double sx, double sy); + TransformationMatrix& scale3d(double sx, double sy, double sz); + + TransformationMatrix& rotate(double d) { return rotate3d(0, 0, d); } + TransformationMatrix& rotateFromVector(double x, double y); + TransformationMatrix& rotate3d(double rx, double ry, double rz); + + // The vector (x,y,z) is normalized if it's not already. A vector of + // (0,0,0) uses a vector of (0,0,1). + TransformationMatrix& rotate3d(double x, double y, double z, double angle); + + TransformationMatrix& translate(double tx, double ty); + TransformationMatrix& translate3d(double tx, double ty, double tz); + + // translation added with a post-multiply + TransformationMatrix& translateRight(double tx, double ty); + TransformationMatrix& translateRight3d(double tx, double ty, double tz); + + TransformationMatrix& flipX(); + TransformationMatrix& flipY(); + TransformationMatrix& skew(double angleX, double angleY); + TransformationMatrix& skewX(double angle) { return skew(angle, 0); } + TransformationMatrix& skewY(double angle) { return skew(0, angle); } + + TransformationMatrix& applyPerspective(double p); + bool hasPerspective() const { return m_matrix[2][3] != 0.0f; } + + bool isInvertible() const; + + // This method returns the identity matrix if it is not invertible. + // Use isInvertible() before calling this if you need to know. + TransformationMatrix inverse() const; + + // decompose the matrix into its component parts + typedef struct { + double scaleX, scaleY, scaleZ; + double skewXY, skewXZ, skewYZ; + double rotateX, rotateY, rotateZ; + double quaternionX, quaternionY, quaternionZ, quaternionW; + double translateX, translateY, translateZ; + double perspectiveX, perspectiveY, perspectiveZ, perspectiveW; + } DecomposedType; + + bool decompose(DecomposedType& decomp) const; + void recompose(const DecomposedType& decomp, bool useEulerAngle = false); + + void blend(const TransformationMatrix& from, double progress); + + bool isAffine() const + { + return (m13() == 0 && m14() == 0 && m23() == 0 && m24() == 0 && + m31() == 0 && m32() == 0 && m33() == 1 && m34() == 0 && m43() == 0 && m44() == 1); + } + + // Throw away the non-affine parts of the matrix (lossy!) + void makeAffine(); + + bool operator==(const TransformationMatrix& m2) const + { + return (m_matrix[0][0] == m2.m_matrix[0][0] && + m_matrix[0][1] == m2.m_matrix[0][1] && + m_matrix[0][2] == m2.m_matrix[0][2] && + m_matrix[0][3] == m2.m_matrix[0][3] && + m_matrix[1][0] == m2.m_matrix[1][0] && + m_matrix[1][1] == m2.m_matrix[1][1] && + m_matrix[1][2] == m2.m_matrix[1][2] && + m_matrix[1][3] == m2.m_matrix[1][3] && + m_matrix[2][0] == m2.m_matrix[2][0] && + m_matrix[2][1] == m2.m_matrix[2][1] && + m_matrix[2][2] == m2.m_matrix[2][2] && + m_matrix[2][3] == m2.m_matrix[2][3] && + m_matrix[3][0] == m2.m_matrix[3][0] && + m_matrix[3][1] == m2.m_matrix[3][1] && + m_matrix[3][2] == m2.m_matrix[3][2] && + m_matrix[3][3] == m2.m_matrix[3][3]); + } + + bool operator!=(const TransformationMatrix& other) const { return !(*this == other); } + + // *this = *this * t (i.e., a multRight) + TransformationMatrix& operator*=(const TransformationMatrix& t) + { + return multiply(t); + } + + // result = *this * t (i.e., a multRight) + TransformationMatrix operator*(const TransformationMatrix& t) + { + TransformationMatrix result = *this; + result.multiply(t); + return result; + } + + CATransform3D transform3d () const; + CGAffineTransform affineTransform () const; + + TransformationMatrix(const CATransform3D&); + operator CATransform3D() const; + + TransformationMatrix(const CGAffineTransform&); + operator CGAffineTransform() const; + + private: + + // multiply passed 2D point by matrix (assume z=0) + void multVecMatrix(double x, double y, double& dstX, double& dstY) const; + + // multiply passed 3D point by matrix + void multVecMatrix(double x, double y, double z, double& dstX, double& dstY, double& dstZ) const; + + void setMatrix(const Matrix4 m) + { + if (m && m != m_matrix) + memcpy(m_matrix, m, sizeof(Matrix4)); + } + + bool isIdentityOrTranslation() const + { + return m_matrix[0][0] == 1 && m_matrix[0][1] == 0 && m_matrix[0][2] == 0 && m_matrix[0][3] == 0 && + m_matrix[1][0] == 0 && m_matrix[1][1] == 1 && m_matrix[1][2] == 0 && m_matrix[1][3] == 0 && + m_matrix[2][0] == 0 && m_matrix[2][1] == 0 && m_matrix[2][2] == 1 && m_matrix[2][3] == 0 && + m_matrix[3][3] == 1; + } + + Matrix4 m_matrix; + }; + +} // namespace WebCore + +#endif // TransformationMatrix_h diff --git a/pop/WebCore/UnitBezier.h b/pop/WebCore/UnitBezier.h new file mode 100644 index 00000000..0f847a0c --- /dev/null +++ b/pop/WebCore/UnitBezier.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UnitBezier_h +#define UnitBezier_h + +#include + +namespace WebCore { + + struct UnitBezier { + UnitBezier(double p1x, double p1y, double p2x, double p2y) + { + // Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1). + cx = 3.0 * p1x; + bx = 3.0 * (p2x - p1x) - cx; + ax = 1.0 - cx -bx; + + cy = 3.0 * p1y; + by = 3.0 * (p2y - p1y) - cy; + ay = 1.0 - cy - by; + } + + double sampleCurveX(double t) + { + // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule. + return ((ax * t + bx) * t + cx) * t; + } + + double sampleCurveY(double t) + { + return ((ay * t + by) * t + cy) * t; + } + + double sampleCurveDerivativeX(double t) + { + return (3.0 * ax * t + 2.0 * bx) * t + cx; + } + + // Given an x value, find a parametric value it came from. + double solveCurveX(double x, double epsilon) + { + double t0; + double t1; + double t2; + double x2; + double d2; + int i; + + // First try a few iterations of Newton's method -- normally very fast. + for (t2 = x, i = 0; i < 8; i++) { + x2 = sampleCurveX(t2) - x; + if (fabs (x2) < epsilon) + return t2; + d2 = sampleCurveDerivativeX(t2); + if (fabs(d2) < 1e-6) + break; + t2 = t2 - x2 / d2; + } + + // Fall back to the bisection method for reliability. + t0 = 0.0; + t1 = 1.0; + t2 = x; + + if (t2 < t0) + return t0; + if (t2 > t1) + return t1; + + while (t0 < t1) { + x2 = sampleCurveX(t2); + if (fabs(x2 - x) < epsilon) + return t2; + if (x > x2) + t0 = t2; + else + t1 = t2; + t2 = (t1 - t0) * .5 + t0; + } + + // Failure. + return t2; + } + + double solve(double x, double epsilon) + { + return sampleCurveY(solveCurveX(x, epsilon)); + } + + private: + double ax; + double bx; + double cx; + + double ay; + double by; + double cy; + }; +} +#endif diff --git a/pop/en.lproj/InfoPlist.strings b/pop/en.lproj/InfoPlist.strings new file mode 100644 index 00000000..477b28ff --- /dev/null +++ b/pop/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/pop/pop-Info.plist b/pop/pop-Info.plist new file mode 100644 index 00000000..50af5f8f --- /dev/null +++ b/pop/pop-Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + + CFBundleIdentifier + com.facebook.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + NSHumanReadableCopyright + Copyright © 2014 Facebook. All rights reserved. + NSPrincipalClass + + + diff --git a/pop/pop-Prefix.pch b/pop/pop-Prefix.pch new file mode 100644 index 00000000..43519d3c --- /dev/null +++ b/pop/pop-Prefix.pch @@ -0,0 +1,7 @@ +// +// Prefix header for all source files of the 'POPAnimation' target in the 'POPAnimation' project +// + +#ifdef __OBJC__ + #import +#endif