lottie-ios 3.2.3 → 3.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/actions/setup/action.yml +32 -0
- package/.github/issue_template.md +6 -23
- package/.github/workflows/main.yml +98 -0
- package/.github/workflows/stale_issues.yml +17 -0
- package/.spi.yml +6 -0
- package/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +7 -0
- package/.swiftpm/xcode/package.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/.swiftpm/xcode/package.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/.swiftpm/xcode/xcuserdata/{brandonwithrow.xcuserdatad → cal.xcuserdatad}/xcschemes/xcschememanagement.plist +25 -15
- package/.swiftpm/xcode/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
- package/Gemfile +4 -0
- package/Gemfile.lock +102 -0
- package/Lottie.xcodeproj/project.pbxproj +2011 -1972
- package/Lottie.xcodeproj/project.xcworkspace/contents.xcworkspacedata +1 -1
- package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie.xcodeproj/xcshareddata/xcschemes/{Lottie_iOS.xcscheme → Lottie (iOS).xcscheme } +15 -18
- package/Lottie.xcodeproj/xcshareddata/xcschemes/{Lottie_tvOS.xcscheme → Lottie (macOS).xcscheme } +5 -18
- package/Lottie.xcodeproj/xcshareddata/xcschemes/{Lottie_macOS.xcscheme → Lottie (tvOS).xcscheme } +5 -18
- package/Lottie.xcodeproj/xcuserdata/cal.xcuserdatad/xcschemes/xcschememanagement.plist +37 -0
- package/Lottie.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +24 -0
- package/Lottie.xcworkspace/contents.xcworkspacedata +10 -0
- package/Lottie.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/Lottie.xcworkspace/xcshareddata/swiftpm/Package.resolved +34 -0
- package/Lottie.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/{Lottie.xcodeproj/xcuserdata/brandonwithrow.xcuserdatad → Lottie.xcworkspace/xcuserdata/cal.xcuserdatad}/xcdebugger/Breakpoints_v2.xcbkptlist +2 -2
- package/Lottie.xcworkspace/xcuserdata/cal.xcuserdatad/xcdebugger/Expressions.xcexplist +153 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +22 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Expressions.xcexplist +138 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +30 -0
- package/Package.swift +5 -15
- package/README.md +40 -60
- package/Rakefile +160 -0
- package/Sources/Private/CoreAnimation/Animations/CAAnimation+TimingConfiguration.swift +81 -0
- package/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift +447 -0
- package/Sources/Private/CoreAnimation/Animations/CombinedShapeAnimation.swift +81 -0
- package/Sources/Private/CoreAnimation/Animations/CustomPathAnimation.swift +41 -0
- package/Sources/Private/CoreAnimation/Animations/EllipseAnimation.swift +55 -0
- package/Sources/Private/CoreAnimation/Animations/GradientAnimations.swift +210 -0
- package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +229 -0
- package/Sources/Private/CoreAnimation/Animations/OpacityAnimation.swift +52 -0
- package/Sources/Private/CoreAnimation/Animations/RectangleAnimation.swift +58 -0
- package/Sources/Private/CoreAnimation/Animations/ShapeAnimation.swift +257 -0
- package/Sources/Private/CoreAnimation/Animations/StarAnimation.swift +124 -0
- package/Sources/Private/CoreAnimation/Animations/StrokeAnimation.swift +73 -0
- package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +205 -0
- package/Sources/Private/CoreAnimation/Animations/VisibilityAnimation.swift +37 -0
- package/Sources/Private/CoreAnimation/CompatibilityTracker.swift +130 -0
- package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +497 -0
- package/Sources/Private/CoreAnimation/Extensions/CALayer+fillBounds.swift +35 -0
- package/Sources/Private/CoreAnimation/Extensions/KeyframeGroup+exactlyOneKeyframe.swift +37 -0
- package/Sources/Private/CoreAnimation/Extensions/Keyframes+combinedIfPossible.swift +154 -0
- package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +83 -0
- package/Sources/Private/CoreAnimation/Layers/BaseAnimationLayer.swift +33 -0
- package/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift +88 -0
- package/Sources/Private/CoreAnimation/Layers/CALayer+setupLayerHierarchy.swift +167 -0
- package/Sources/Private/CoreAnimation/Layers/GradientRenderLayer.swift +94 -0
- package/Sources/Private/CoreAnimation/Layers/ImageLayer.swift +79 -0
- package/Sources/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift +64 -0
- package/Sources/Private/CoreAnimation/Layers/MaskCompositionLayer.swift +138 -0
- package/Sources/Private/CoreAnimation/Layers/PreCompLayer.swift +140 -0
- package/Sources/Private/CoreAnimation/Layers/RepeaterLayer.swift +85 -0
- package/Sources/Private/CoreAnimation/Layers/ShapeItemLayer.swift +315 -0
- package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +390 -0
- package/Sources/Private/CoreAnimation/Layers/SolidLayer.swift +47 -0
- package/Sources/Private/CoreAnimation/Layers/TextLayer.swift +91 -0
- package/Sources/Private/CoreAnimation/Layers/TransformLayer.swift +11 -0
- package/Sources/Private/CoreAnimation/ValueProviderStore.swift +139 -0
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/CompositionLayer.swift +94 -86
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/ImageCompositionLayer.swift +24 -21
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/MaskContainerLayer.swift +75 -54
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/NullCompositionLayer.swift +5 -5
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/PreCompositionLayer.swift +59 -43
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/ShapeCompositionLayer.swift +22 -20
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/SolidCompositionLayer.swift +26 -17
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/TextCompositionLayer.swift +42 -36
- package/{lottie-swift/src/Private/LayerContainers/AnimationContainer.swift → Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift} +200 -140
- package/Sources/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift +47 -0
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/CompositionLayersInitializer.swift +33 -24
- package/{lottie-swift/src/Private/LayerContainers/Utility/TextLayer.swift → Sources/Private/MainThread/LayerContainers/Utility/CoreTextRenderLayer.swift} +149 -106
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/InvertedMatteLayer.swift +26 -24
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift +41 -0
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/LayerImageProvider.swift +20 -16
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift +40 -0
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/LayerTransformNode.swift +82 -67
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Extensions/ItemsExtension.swift +9 -4
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/NodeProperty.swift +29 -21
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift +16 -10
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift +6 -6
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift +5 -5
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift +10 -8
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/ValueContainer.swift +25 -21
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift +23 -17
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift +22 -17
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift +110 -79
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift +22 -16
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift +19 -15
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift +22 -20
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift +22 -23
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift +247 -0
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift +30 -24
- package/{lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift → Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift} +93 -66
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift +23 -19
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift +139 -0
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift +170 -0
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/PathNodes/RectNode.swift +95 -58
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift +42 -29
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/PathNodes/StarNode.swift +108 -69
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderContainers/GroupNode.swift +155 -0
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift +51 -37
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +57 -42
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +66 -58
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +169 -0
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +138 -119
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Protocols/AnimatorNode.swift +80 -79
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Protocols/PathNode.swift +5 -3
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Protocols/RenderNode.swift +22 -17
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift +27 -24
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift +34 -25
- package/Sources/Private/Model/Animation.swift +160 -0
- package/Sources/Private/Model/Assets/Asset.swift +43 -0
- package/{lottie-swift/src → Sources}/Private/Model/Assets/AssetLibrary.swift +40 -13
- package/Sources/Private/Model/Assets/ImageAsset.swift +112 -0
- package/{lottie-swift/src → Sources}/Private/Model/Assets/PrecompAsset.swift +20 -10
- package/Sources/Private/Model/DictionaryInitializable.swift +67 -0
- package/Sources/Private/Model/Extensions/Bundle.swift +34 -0
- package/{lottie-swift/src → Sources}/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift +8 -4
- package/Sources/Private/Model/Keyframes/KeyframeData.swift +113 -0
- package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +222 -0
- package/{lottie-swift/src → Sources}/Private/Model/Layers/ImageLayerModel.swift +21 -11
- package/Sources/Private/Model/Layers/LayerModel.swift +228 -0
- package/{lottie-swift/src → Sources}/Private/Model/Layers/PreCompLayerModel.swift +39 -22
- package/{lottie-swift/src → Sources}/Private/Model/Layers/ShapeLayerModel.swift +23 -12
- package/{lottie-swift/src → Sources}/Private/Model/Layers/SolidLayerModel.swift +31 -19
- package/{lottie-swift/src → Sources}/Private/Model/Layers/TextLayerModel.swift +33 -19
- package/Sources/Private/Model/Objects/DashPattern.swift +44 -0
- package/Sources/Private/Model/Objects/Marker.swift +33 -0
- package/Sources/Private/Model/Objects/Mask.swift +80 -0
- package/Sources/Private/Model/Objects/Transform.swift +179 -0
- package/Sources/Private/Model/ShapeItems/Ellipse.swift +74 -0
- package/Sources/Private/Model/ShapeItems/Fill.swift +74 -0
- package/Sources/Private/Model/ShapeItems/GradientFill.swift +137 -0
- package/Sources/Private/Model/ShapeItems/GradientStroke.swift +185 -0
- package/Sources/Private/Model/ShapeItems/Group.swift +48 -0
- package/{lottie-swift/src → Sources}/Private/Model/ShapeItems/Merge.swift +29 -12
- package/Sources/Private/Model/ShapeItems/Rectangle.swift +72 -0
- package/Sources/Private/Model/ShapeItems/Repeater.swift +135 -0
- package/Sources/Private/Model/ShapeItems/Shape.swift +56 -0
- package/Sources/Private/Model/ShapeItems/ShapeItem.swift +163 -0
- package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +135 -0
- package/Sources/Private/Model/ShapeItems/Star.swift +131 -0
- package/Sources/Private/Model/ShapeItems/Stroke.swift +101 -0
- package/Sources/Private/Model/ShapeItems/Trim.swift +77 -0
- package/Sources/Private/Model/Text/Font.swift +61 -0
- package/{lottie-swift/src → Sources}/Private/Model/Text/Glyph.swift +63 -39
- package/Sources/Private/Model/Text/TextAnimator.swift +164 -0
- package/Sources/Private/Model/Text/TextDocument.swift +123 -0
- package/Sources/Private/RootAnimationLayer.swift +51 -0
- package/{lottie-swift/src → Sources}/Private/Utility/Debugging/AnimatorNodeDebugging.swift +7 -7
- package/{lottie-swift/src → Sources}/Private/Utility/Debugging/LayerDebugging.swift +72 -48
- package/Sources/Private/Utility/Debugging/TestHelpers.swift +10 -0
- package/{lottie-swift/src → Sources}/Private/Utility/Extensions/AnimationKeypathExtension.swift +69 -59
- package/Sources/Private/Utility/Extensions/BlendMode+Filter.swift +31 -0
- package/Sources/Private/Utility/Extensions/CGColor+RGB.swift +22 -0
- package/{lottie-swift/src → Sources}/Private/Utility/Extensions/CGFloatExtensions.swift +70 -67
- package/Sources/Private/Utility/Extensions/DataExtension.swift +27 -0
- package/Sources/Private/Utility/Extensions/MathKit.swift +450 -0
- package/Sources/Private/Utility/Extensions/StringExtensions.swift +38 -0
- package/{lottie-swift/src → Sources}/Private/Utility/Helpers/AnimationContext.swift +42 -16
- package/Sources/Private/Utility/Interpolatable/InterpolatableExtensions.swift +134 -0
- package/{lottie-swift/src → Sources}/Private/Utility/Interpolatable/KeyframeExtensions.swift +13 -10
- package/Sources/Private/Utility/Interpolatable/KeyframeGroup+Extensions.swift +59 -0
- package/{lottie-swift/src/Private/NodeRenderSystem/NodeProperties/ValueProviders → Sources/Private/Utility/Interpolatable}/KeyframeInterpolator.swift +125 -108
- package/{lottie-swift/src → Sources}/Private/Utility/Primitives/BezierPath.swift +229 -143
- package/Sources/Private/Utility/Primitives/CGPointExtension.swift +35 -0
- package/{lottie-swift/src → Sources}/Private/Utility/Primitives/ColorExtension.swift +46 -14
- package/{lottie-swift/src → Sources}/Private/Utility/Primitives/CompoundBezierPath.swift +58 -49
- package/Sources/Private/Utility/Primitives/CurveVertex.swift +184 -0
- package/Sources/Private/Utility/Primitives/PathElement.swift +75 -0
- package/Sources/Private/Utility/Primitives/UnitBezier.swift +115 -0
- package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +341 -0
- package/Sources/Public/Animation/AnimationPublic.swift +266 -0
- package/Sources/Public/Animation/AnimationView.swift +1335 -0
- package/Sources/Public/Animation/AnimationViewInitializers.swift +109 -0
- package/Sources/Public/AnimationCache/AnimationCacheProvider.swift +22 -0
- package/{lottie-swift/src → Sources}/Public/AnimationCache/LRUAnimationCache.swift +22 -18
- package/Sources/Public/DynamicProperties/AnimationKeypath.swift +49 -0
- package/Sources/Public/DynamicProperties/AnyValueProvider.swift +132 -0
- package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift +54 -36
- package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift +40 -36
- package/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +123 -0
- package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/PointValueProvider.swift +40 -35
- package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift +40 -35
- package/{lottie-swift/src → Sources}/Public/FontProvider/AnimationFontProvider.swift +15 -8
- package/Sources/Public/ImageProvider/AnimationImageProvider.swift +21 -0
- package/Sources/Public/Keyframes/Interpolatable.swift +253 -0
- package/Sources/Public/Keyframes/Keyframe.swift +92 -0
- package/Sources/Public/Logging/LottieLogger.swift +137 -0
- package/Sources/Public/LottieConfiguration.swift +143 -0
- package/{lottie-swift/src → Sources}/Public/Primitives/AnimationTime.swift +1 -1
- package/{lottie-swift/src → Sources}/Public/Primitives/Color.swift +10 -6
- package/{lottie-swift/src → Sources}/Public/Primitives/Vectors.swift +13 -12
- package/Sources/Public/TextProvider/AnimationTextProvider.swift +53 -0
- package/{lottie-swift/src → Sources}/Public/iOS/AnimatedButton.swift +42 -26
- package/{lottie-swift/src → Sources}/Public/iOS/AnimatedControl.swift +100 -91
- package/{lottie-swift/src → Sources}/Public/iOS/AnimatedSwitch.swift +128 -94
- package/{lottie-swift/src → Sources}/Public/iOS/AnimationSubview.swift +3 -3
- package/Sources/Public/iOS/AnimationViewBase.swift +78 -0
- package/{lottie-swift/src → Sources}/Public/iOS/BundleImageProvider.swift +36 -34
- package/{lottie-swift/src → Sources}/Public/iOS/Compatibility/CompatibleAnimationKeypath.swift +4 -0
- package/{lottie-swift/src → Sources}/Public/iOS/Compatibility/CompatibleAnimationView.swift +38 -29
- package/{lottie-swift/src → Sources}/Public/iOS/FilepathImageProvider.swift +24 -21
- package/{lottie-swift/src → Sources}/Public/iOS/UIColorExtension.swift +4 -4
- package/{lottie-swift/src/Public/MacOS/AnimationSubview.swift → Sources/Public/macOS/AnimationSubview.macOS.swift} +4 -5
- package/{lottie-swift/src/Public/MacOS/LottieView.swift → Sources/Public/macOS/AnimationViewBase.macOS.swift} +58 -50
- package/{lottie-swift/src/Public/MacOS/BundleImageProvider.swift → Sources/Public/macOS/BundleImageProvider.macOS.swift} +31 -32
- package/Sources/Public/macOS/FilepathImageProvider.macOS.swift +67 -0
- package/Tests/AnimationKeypathTests.swift +103 -0
- package/Tests/AutomaticEngineTests.swift +57 -0
- package/Tests/BundleTests.swift +25 -0
- package/Tests/DataURLTests.swift +64 -0
- package/Tests/ParsingTests.swift +43 -0
- package/Tests/PerformanceTests.swift +214 -0
- package/Tests/Samples/9squares_AlBoardman.json +1 -0
- package/Tests/Samples/Boat_Loader.json +1 -0
- package/Tests/Samples/HamburgerArrow.json +1 -0
- package/Tests/Samples/IconTransitions.json +1 -0
- package/Tests/Samples/Images/dog.png +0 -0
- package/Tests/Samples/Issues/issue_1125.json +1 -0
- package/Tests/Samples/Issues/issue_1260.json +1 -0
- package/Tests/Samples/Issues/issue_1403.json +1 -0
- package/Tests/Samples/Issues/issue_1407.json +1 -0
- package/Tests/Samples/Issues/issue_1460.json +1 -0
- package/Tests/Samples/Issues/issue_1488.json +1 -0
- package/Tests/Samples/Issues/issue_1505.json +1 -0
- package/Tests/Samples/Issues/issue_1541.json +1 -0
- package/Tests/Samples/Issues/issue_1557.json +1 -0
- package/Tests/Samples/Issues/issue_1603.json +1 -0
- package/Tests/Samples/Issues/issue_1628.json +1 -0
- package/Tests/Samples/Issues/issue_1636.json +1 -0
- package/Tests/Samples/Issues/issue_1643.json +1 -0
- package/Tests/Samples/Issues/issue_1655.json +1 -0
- package/Tests/Samples/Issues/issue_1664.json +1 -0
- package/Tests/Samples/Issues/issue_1683.json +1 -0
- package/Tests/Samples/Issues/issue_1687.json +1 -0
- package/Tests/Samples/Issues/issue_1711.json +1 -0
- package/Tests/Samples/Issues/issue_1717.json +1 -0
- package/Tests/Samples/Issues/issue_769.json +1 -0
- package/Tests/Samples/Issues/issue_885.json +1 -0
- package/Tests/Samples/Issues/issue_965.json +1 -0
- package/Tests/Samples/Issues/pr_1536.json +1 -0
- package/Tests/Samples/Issues/pr_1563.json +8439 -0
- package/Tests/Samples/Issues/pr_1592.json +5527 -0
- package/Tests/Samples/Issues/pr_1599.json +738 -0
- package/Tests/Samples/Issues/pr_1604_1.json +1 -0
- package/Tests/Samples/Issues/pr_1604_2.json +1 -0
- package/Tests/Samples/Issues/pr_1632_1.json +1 -0
- package/Tests/Samples/Issues/pr_1632_2.json +1 -0
- package/Tests/Samples/Issues/pr_1686.json +513 -0
- package/Tests/Samples/Issues/pr_1698.json +1 -0
- package/Tests/Samples/Issues/pr_1699.json +1 -0
- package/Tests/Samples/LottieFiles/LICENSE.md +14 -0
- package/Tests/Samples/LottieFiles/bounce_strokes.json +1 -0
- package/Tests/Samples/LottieFiles/cactus.json +1 -0
- package/Tests/Samples/LottieFiles/dog_car_ride.json +1 -0
- package/Tests/Samples/LottieFiles/draft_icon.json +1 -0
- package/Tests/Samples/LottieFiles/fireworks.json +1 -0
- package/Tests/Samples/LottieFiles/gradient_1.json +1 -0
- package/Tests/Samples/LottieFiles/gradient_2.json +1 -0
- package/Tests/Samples/LottieFiles/gradient_pill.json +1 -0
- package/Tests/Samples/LottieFiles/gradient_shapes.json +1 -0
- package/Tests/Samples/LottieFiles/gradient_square.json +1 -0
- package/Tests/Samples/LottieFiles/growth.json +1 -0
- package/Tests/Samples/LottieFiles/infinity_loader.json +1 -0
- package/Tests/Samples/LottieFiles/loading_dots_1.json +1 -0
- package/Tests/Samples/LottieFiles/loading_dots_2.json +1 -0
- package/Tests/Samples/LottieFiles/loading_dots_3.json +1 -0
- package/Tests/Samples/LottieFiles/loading_gradient_strokes.json +1 -0
- package/Tests/Samples/LottieFiles/settings_slider.json +1 -0
- package/Tests/Samples/LottieFiles/shop.json +1 -0
- package/Tests/Samples/LottieFiles/step_loader.json +1 -0
- package/Tests/Samples/LottieLogo1.json +1 -0
- package/Tests/Samples/LottieLogo1_masked.json +1 -0
- package/Tests/Samples/LottieLogo2.json +1 -0
- package/Tests/Samples/MotionCorpse_Jrcanest.json +1 -0
- package/Tests/Samples/Nonanimating/BasicLayers.json +1 -0
- package/Tests/Samples/Nonanimating/DisableNodesTest.json +1 -0
- package/Tests/Samples/Nonanimating/FirstText.json +1 -0
- package/Tests/Samples/Nonanimating/GeometryTransformTest.json +1 -0
- package/Tests/Samples/Nonanimating/Text_AnimatedProperties.json +1 -0
- package/Tests/Samples/Nonanimating/Text_Glyph.json +1 -0
- package/Tests/Samples/Nonanimating/Text_NoAnimation.json +1 -0
- package/Tests/Samples/Nonanimating/Text_NoGlyph.json +1 -0
- package/Tests/Samples/Nonanimating/Zoom.json +1 -0
- package/Tests/Samples/Nonanimating/_dog.json +1 -0
- package/Tests/Samples/Nonanimating/base64Test.json +1 -0
- package/Tests/Samples/Nonanimating/blend_mode_test.json +1 -0
- package/Tests/Samples/Nonanimating/keypathTest.json +1 -0
- package/Tests/Samples/Nonanimating/verifyLineHeight.json +1 -0
- package/Tests/Samples/PinJump.json +1 -0
- package/Tests/Samples/Switch.json +1 -0
- package/Tests/Samples/Switch_States.json +1 -0
- package/Tests/Samples/TwitterHeart.json +1 -0
- package/Tests/Samples/TwitterHeartButton.json +1 -0
- package/Tests/Samples/TypeFace/A.json +1 -0
- package/Tests/Samples/TypeFace/Apostrophe.json +1 -0
- package/Tests/Samples/TypeFace/B.json +1 -0
- package/Tests/Samples/TypeFace/BlinkingCursor.json +1 -0
- package/Tests/Samples/TypeFace/C.json +1 -0
- package/Tests/Samples/TypeFace/Colon.json +1 -0
- package/Tests/Samples/TypeFace/Comma.json +1 -0
- package/Tests/Samples/TypeFace/D.json +1 -0
- package/Tests/Samples/TypeFace/E.json +1 -0
- package/Tests/Samples/TypeFace/F.json +1 -0
- package/Tests/Samples/TypeFace/G.json +1 -0
- package/Tests/Samples/TypeFace/H.json +1 -0
- package/Tests/Samples/TypeFace/I.json +1 -0
- package/Tests/Samples/TypeFace/J.json +1 -0
- package/Tests/Samples/TypeFace/K.json +1 -0
- package/Tests/Samples/TypeFace/L.json +1 -0
- package/Tests/Samples/TypeFace/M.json +1 -0
- package/Tests/Samples/TypeFace/N.json +1 -0
- package/Tests/Samples/TypeFace/O.json +1 -0
- package/Tests/Samples/TypeFace/P.json +1 -0
- package/Tests/Samples/TypeFace/Q.json +1 -0
- package/Tests/Samples/TypeFace/R.json +1 -0
- package/Tests/Samples/TypeFace/S.json +1 -0
- package/Tests/Samples/TypeFace/T.json +1 -0
- package/Tests/Samples/TypeFace/U.json +1 -0
- package/Tests/Samples/TypeFace/V.json +1 -0
- package/Tests/Samples/TypeFace/W.json +1 -0
- package/Tests/Samples/TypeFace/X.json +1 -0
- package/Tests/Samples/TypeFace/Y.json +1 -0
- package/Tests/Samples/TypeFace/Z.json +1 -0
- package/Tests/Samples/Watermelon.json +1 -0
- package/Tests/Samples/setValueTest.json +1 -0
- package/Tests/Samples/timeremap.json +1 -0
- package/Tests/Samples/vcTransition1.json +1 -0
- package/Tests/Samples/vcTransition2.json +1 -0
- package/Tests/SnapshotConfiguration.swift +158 -0
- package/Tests/SnapshotTests.swift +265 -0
- package/Tests/Utils/Bundle+Module.swift +30 -0
- package/Tests/Utils/HardcodedFontProvider.swift +19 -0
- package/Tests/Utils/HardcodedImageProvider.swift +23 -0
- package/Tests/Utils/Snapshotting+presentationLayer.swift +47 -0
- package/Tests/ValueProvidersTests.swift +27 -0
- package/lottie-ios.podspec +12 -12
- package/package.json +1 -1
- package/script/test-carthage/Cartfile +1 -0
- package/script/test-carthage/Cartfile.resolved +1 -0
- package/script/test-carthage/CarthageTest/AppDelegate.swift +26 -0
- package/script/test-carthage/CarthageTest/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
- package/script/test-carthage/CarthageTest/Assets.xcassets/AppIcon.appiconset/Contents.json +98 -0
- package/script/test-carthage/CarthageTest/Assets.xcassets/Contents.json +6 -0
- package/script/test-carthage/CarthageTest/Base.lproj/LaunchScreen.storyboard +25 -0
- package/script/test-carthage/CarthageTest/Base.lproj/Main.storyboard +24 -0
- package/script/test-carthage/CarthageTest/Info.plist +66 -0
- package/script/test-carthage/CarthageTest/SceneDelegate.swift +10 -0
- package/script/test-carthage/CarthageTest/ViewController.swift +15 -0
- package/script/test-carthage/CarthageTest-macOS/AppDelegate.swift +7 -0
- package/script/test-carthage/CarthageTest-macOS/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
- package/script/test-carthage/CarthageTest-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json +58 -0
- package/script/test-carthage/CarthageTest-macOS/Assets.xcassets/Contents.json +6 -0
- package/script/test-carthage/CarthageTest-macOS/Base.lproj/Main.storyboard +717 -0
- package/script/test-carthage/CarthageTest-macOS/CarthageTest_macOS.entitlements +10 -0
- package/script/test-carthage/CarthageTest-macOS/ViewController.swift +15 -0
- package/script/test-carthage/CarthageTest.xcodeproj/project.pbxproj +532 -0
- package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/script/test-carthage/CarthageTest.xcodeproj/xcuserdata/cal.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
- package/script/test-carthage/CarthageTest.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +19 -0
- package/script/test-carthage/Mintfile +1 -0
- package/script/test-spm/LottieSPM.xcworkspace/contents.xcworkspacedata +7 -0
- package/script/test-spm/LottieSPM.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/script/test-spm/Mintfile +1 -0
- package/.swiftpm/xcode/package.xcworkspace/xcuserdata/brandonwithrow.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie/Info.plist +0 -22
- package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/brandonwithrow.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie.xcodeproj/xcuserdata/brandonwithrow.xcuserdatad/xcschemes/LottieLibraryMacOS.xcscheme +0 -80
- package/Lottie.xcodeproj/xcuserdata/brandonwithrow.xcuserdatad/xcschemes/xcschememanagement.plist +0 -57
- package/lottie-swift/Assets/.gitkeep +0 -0
- package/lottie-swift/src/Private/LayerContainers/Utility/LayerFontProvider.swift +0 -37
- package/lottie-swift/src/Private/LayerContainers/Utility/LayerTextProvider.swift +0 -36
- package/lottie-swift/src/Private/Model/Animation.swift +0 -107
- package/lottie-swift/src/Private/Model/Assets/Asset.swift +0 -27
- package/lottie-swift/src/Private/Model/Assets/ImageAsset.swift +0 -48
- package/lottie-swift/src/Private/Model/Keyframes/Keyframe.swift +0 -128
- package/lottie-swift/src/Private/Model/Keyframes/KeyframeGroup.swift +0 -108
- package/lottie-swift/src/Private/Model/Layers/LayerModel.swift +0 -150
- package/lottie-swift/src/Private/Model/Objects/DashPattern.swift +0 -24
- package/lottie-swift/src/Private/Model/Objects/Marker.swift +0 -23
- package/lottie-swift/src/Private/Model/Objects/Mask.swift +0 -48
- package/lottie-swift/src/Private/Model/Objects/Transform.swift +0 -105
- package/lottie-swift/src/Private/Model/ShapeItems/Ellipse.swift +0 -50
- package/lottie-swift/src/Private/Model/ShapeItems/FillI.swift +0 -49
- package/lottie-swift/src/Private/Model/ShapeItems/GradientFill.swift +0 -86
- package/lottie-swift/src/Private/Model/ShapeItems/GradientStroke.swift +0 -125
- package/lottie-swift/src/Private/Model/ShapeItems/Group.swift +0 -32
- package/lottie-swift/src/Private/Model/ShapeItems/Rectangle.swift +0 -50
- package/lottie-swift/src/Private/Model/ShapeItems/Repeater.swift +0 -80
- package/lottie-swift/src/Private/Model/ShapeItems/Shape.swift +0 -37
- package/lottie-swift/src/Private/Model/ShapeItems/ShapeItem.swift +0 -95
- package/lottie-swift/src/Private/Model/ShapeItems/ShapeTransform.swift +0 -68
- package/lottie-swift/src/Private/Model/ShapeItems/Star.swift +0 -86
- package/lottie-swift/src/Private/Model/ShapeItems/Stroke.swift +0 -67
- package/lottie-swift/src/Private/Model/ShapeItems/Trim.swift +0 -53
- package/lottie-swift/src/Private/Model/Text/Font.swift +0 -35
- package/lottie-swift/src/Private/Model/Text/TextAnimator.swift +0 -99
- package/lottie-swift/src/Private/Model/Text/TextDocument.swift +0 -70
- package/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift +0 -109
- package/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift +0 -132
- package/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderContainers/GroupNode.swift +0 -141
- package/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +0 -127
- package/lottie-swift/src/Private/Utility/Extensions/MathKit.swift +0 -539
- package/lottie-swift/src/Private/Utility/Extensions/StringExtensions.swift +0 -32
- package/lottie-swift/src/Private/Utility/Interpolatable/Interpolatable.swift +0 -18
- package/lottie-swift/src/Private/Utility/Interpolatable/InterpolatableExtensions.swift +0 -170
- package/lottie-swift/src/Private/Utility/Primitives/CurveVertex.swift +0 -177
- package/lottie-swift/src/Private/Utility/Primitives/PathElement.swift +0 -68
- package/lottie-swift/src/Private/Utility/Primitives/VectorsExtensions.swift +0 -218
- package/lottie-swift/src/Public/Animation/AnimationPublic.swift +0 -196
- package/lottie-swift/src/Public/Animation/AnimationView.swift +0 -1006
- package/lottie-swift/src/Public/Animation/AnimationViewInitializers.swift +0 -83
- package/lottie-swift/src/Public/AnimationCache/AnimationCacheProvider.swift +0 -24
- package/lottie-swift/src/Public/DynamicProperties/AnimationKeypath.swift +0 -46
- package/lottie-swift/src/Public/DynamicProperties/AnyValueProvider.swift +0 -29
- package/lottie-swift/src/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +0 -114
- package/lottie-swift/src/Public/ImageProvider/AnimationImageProvider.swift +0 -23
- package/lottie-swift/src/Public/MacOS/FilepathImageProvider.swift +0 -67
- package/lottie-swift/src/Public/TextProvider/AnimationTextProvider.swift +0 -39
- package/lottie-swift/src/Public/iOS/LottieView.swift +0 -62
- package/lottie-swift-testing.podspec +0 -32
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
// Created by Cal Stephens on 12/13/21.
|
|
2
|
+
// Copyright © 2021 Airbnb Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
import QuartzCore
|
|
5
|
+
|
|
6
|
+
// MARK: - ShapeItemLayer
|
|
7
|
+
|
|
8
|
+
/// A CALayer type that renders an array of `[ShapeItem]`s,
|
|
9
|
+
/// from a `Group` in a `ShapeLayerModel`.
|
|
10
|
+
final class ShapeItemLayer: BaseAnimationLayer {
|
|
11
|
+
|
|
12
|
+
// MARK: Lifecycle
|
|
13
|
+
|
|
14
|
+
/// Initializes a `ShapeItemLayer` that renders a `Group` from a `ShapeLayerModel`
|
|
15
|
+
/// - Parameters:
|
|
16
|
+
/// - shape: The `ShapeItem` in this group that renders a `GGPath`
|
|
17
|
+
/// - otherItems: Other items in this group that affect the appearance of the shape
|
|
18
|
+
init(shape: Item, otherItems: [Item], context: LayerContext) throws {
|
|
19
|
+
self.shape = shape
|
|
20
|
+
self.otherItems = otherItems
|
|
21
|
+
|
|
22
|
+
try context.compatibilityAssert(
|
|
23
|
+
shape.item.drawsCGPath,
|
|
24
|
+
"`ShapeItemLayer` must contain exactly one `ShapeItem` that draws a `GPPath`")
|
|
25
|
+
|
|
26
|
+
try context.compatibilityAssert(
|
|
27
|
+
!otherItems.contains(where: { $0.item.drawsCGPath }),
|
|
28
|
+
"`ShapeItemLayer` must contain exactly one `ShapeItem` that draws a `GPPath`")
|
|
29
|
+
|
|
30
|
+
super.init()
|
|
31
|
+
|
|
32
|
+
setupLayerHierarchy()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
required init?(coder _: NSCoder) {
|
|
36
|
+
fatalError("init(coder:) has not been implemented")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Called by CoreAnimation to create a shadow copy of this layer
|
|
40
|
+
/// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init
|
|
41
|
+
override init(layer: Any) {
|
|
42
|
+
guard let typedLayer = layer as? Self else {
|
|
43
|
+
fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
shape = typedLayer.shape
|
|
47
|
+
otherItems = typedLayer.otherItems
|
|
48
|
+
super.init(layer: typedLayer)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// MARK: Internal
|
|
52
|
+
|
|
53
|
+
/// An item that can be displayed by this layer
|
|
54
|
+
struct Item {
|
|
55
|
+
/// A `ShapeItem` that should be rendered by this layer
|
|
56
|
+
let item: ShapeItem
|
|
57
|
+
|
|
58
|
+
/// The set of groups that this item descends from
|
|
59
|
+
/// - Due to the way `GroupLayer`s are setup, the original `ShapeItem`
|
|
60
|
+
/// hierarchy from the `ShapeLayer` data model may no longer exactly
|
|
61
|
+
/// match the hierarchy of `GroupLayer` / `ShapeItemLayer`s constructed
|
|
62
|
+
/// at runtime. Since animation keypaths need to match the original
|
|
63
|
+
/// structure of the `ShapeLayer` data model, we track that info here.
|
|
64
|
+
let groupPath: [String]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
override func setupAnimations(context: LayerAnimationContext) throws {
|
|
68
|
+
try super.setupAnimations(context: context)
|
|
69
|
+
|
|
70
|
+
guard let sublayerConfiguration = sublayerConfiguration else { return }
|
|
71
|
+
|
|
72
|
+
switch sublayerConfiguration.fill {
|
|
73
|
+
case .solidFill(let shapeLayer):
|
|
74
|
+
try setupSolidFillAnimations(shapeLayer: shapeLayer, context: context)
|
|
75
|
+
|
|
76
|
+
case .gradientFill(let gradientLayers):
|
|
77
|
+
try setupGradientFillAnimations(layers: gradientLayers, context: context)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if let gradientStrokeConfiguration = sublayerConfiguration.gradientStroke {
|
|
81
|
+
try setupGradientStrokeAnimations(layers: gradientStrokeConfiguration, context: context)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// MARK: Private
|
|
86
|
+
|
|
87
|
+
private struct GradientLayers {
|
|
88
|
+
/// The `CALayer` that renders the RGB components of the gradient
|
|
89
|
+
let gradientColorLayer: GradientRenderLayer
|
|
90
|
+
/// The `CALayer` that renders the alpha components of the gradient,
|
|
91
|
+
/// masking the `gradientColorLayer`
|
|
92
|
+
let gradientAlphaLayer: GradientRenderLayer?
|
|
93
|
+
/// The `CAShapeLayer` that clips the gradient layers to the expected shape
|
|
94
|
+
let shapeMaskLayer: CAShapeLayer
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// The configuration of this layer's `fill` sublayers
|
|
98
|
+
private enum FillLayerConfiguration {
|
|
99
|
+
/// This layer displays a single `CAShapeLayer`
|
|
100
|
+
case solidFill(CAShapeLayer)
|
|
101
|
+
|
|
102
|
+
/// This layer displays a `GradientRenderLayer` masked by a `CAShapeLayer`.
|
|
103
|
+
case gradientFill(GradientLayers)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// The `ShapeItem` in this group that renders a `GGPath`
|
|
107
|
+
private let shape: Item
|
|
108
|
+
|
|
109
|
+
/// Other items in this group that affect the appearance of the shape
|
|
110
|
+
private let otherItems: [Item]
|
|
111
|
+
|
|
112
|
+
/// The current configuration of this layer's sublayer(s)
|
|
113
|
+
private var sublayerConfiguration: (fill: FillLayerConfiguration, gradientStroke: GradientLayers?)?
|
|
114
|
+
|
|
115
|
+
private func setupLayerHierarchy() {
|
|
116
|
+
// We have to build a different layer hierarchy depending on if
|
|
117
|
+
// we're rendering a gradient (a `CAGradientLayer` masked by a `CAShapeLayer`)
|
|
118
|
+
// or a solid shape (a simple `CAShapeLayer`).
|
|
119
|
+
let fillLayerConfiguration: FillLayerConfiguration
|
|
120
|
+
if let gradientFill = otherItems.first(GradientFill.self) {
|
|
121
|
+
fillLayerConfiguration = setupGradientFillLayerHierarchy(for: gradientFill)
|
|
122
|
+
} else {
|
|
123
|
+
fillLayerConfiguration = setupSolidFillLayerHierarchy()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let gradientStrokeConfiguration: GradientLayers?
|
|
127
|
+
if let gradientStroke = otherItems.first(GradientStroke.self) {
|
|
128
|
+
gradientStrokeConfiguration = setupGradientStrokeLayerHierarchy(for: gradientStroke)
|
|
129
|
+
} else {
|
|
130
|
+
gradientStrokeConfiguration = nil
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
sublayerConfiguration = (fillLayerConfiguration, gradientStrokeConfiguration)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private func setupSolidFillLayerHierarchy() -> FillLayerConfiguration {
|
|
137
|
+
let shapeLayer = CAShapeLayer()
|
|
138
|
+
addSublayer(shapeLayer)
|
|
139
|
+
|
|
140
|
+
// `CAShapeLayer.fillColor` defaults to black, so we have to
|
|
141
|
+
// nil out the background color if there isn't an expected fill color
|
|
142
|
+
if !otherItems.contains(where: { $0.item is Fill }) {
|
|
143
|
+
shapeLayer.fillColor = nil
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return .solidFill(shapeLayer)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private func setupGradientFillLayerHierarchy(
|
|
150
|
+
for gradientFill: GradientFill)
|
|
151
|
+
-> FillLayerConfiguration
|
|
152
|
+
{
|
|
153
|
+
let pathMask = CAShapeLayer()
|
|
154
|
+
pathMask.fillColor = .rgb(0, 0, 0)
|
|
155
|
+
mask = pathMask
|
|
156
|
+
|
|
157
|
+
let rgbGradientLayer = GradientRenderLayer()
|
|
158
|
+
addSublayer(rgbGradientLayer)
|
|
159
|
+
|
|
160
|
+
let alphaGradientLayer: GradientRenderLayer?
|
|
161
|
+
if gradientFill.hasAlphaComponent {
|
|
162
|
+
alphaGradientLayer = GradientRenderLayer()
|
|
163
|
+
rgbGradientLayer.mask = alphaGradientLayer
|
|
164
|
+
} else {
|
|
165
|
+
alphaGradientLayer = nil
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return .gradientFill(GradientLayers(
|
|
169
|
+
gradientColorLayer: rgbGradientLayer,
|
|
170
|
+
gradientAlphaLayer: alphaGradientLayer,
|
|
171
|
+
shapeMaskLayer: pathMask))
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private func setupGradientStrokeLayerHierarchy(
|
|
175
|
+
for gradientStroke: GradientStroke)
|
|
176
|
+
-> GradientLayers
|
|
177
|
+
{
|
|
178
|
+
let container = BaseAnimationLayer()
|
|
179
|
+
|
|
180
|
+
let pathMask = CAShapeLayer()
|
|
181
|
+
pathMask.fillColor = nil
|
|
182
|
+
pathMask.strokeColor = .rgb(0, 0, 0)
|
|
183
|
+
container.mask = pathMask
|
|
184
|
+
|
|
185
|
+
let rgbGradientLayer = GradientRenderLayer()
|
|
186
|
+
container.addSublayer(rgbGradientLayer)
|
|
187
|
+
addSublayer(container)
|
|
188
|
+
|
|
189
|
+
let alphaGradientLayer: GradientRenderLayer?
|
|
190
|
+
if gradientStroke.hasAlphaComponent {
|
|
191
|
+
alphaGradientLayer = GradientRenderLayer()
|
|
192
|
+
rgbGradientLayer.mask = alphaGradientLayer
|
|
193
|
+
} else {
|
|
194
|
+
alphaGradientLayer = nil
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return GradientLayers(
|
|
198
|
+
gradientColorLayer: rgbGradientLayer,
|
|
199
|
+
gradientAlphaLayer: alphaGradientLayer,
|
|
200
|
+
shapeMaskLayer: pathMask)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private func setupSolidFillAnimations(
|
|
204
|
+
shapeLayer: CAShapeLayer,
|
|
205
|
+
context: LayerAnimationContext)
|
|
206
|
+
throws
|
|
207
|
+
{
|
|
208
|
+
var trimPathMultiplier: PathMultiplier? = nil
|
|
209
|
+
if let (trim, context) = otherItems.first(Trim.self, context: context) {
|
|
210
|
+
trimPathMultiplier = try shapeLayer.addAnimations(for: trim, context: context)
|
|
211
|
+
|
|
212
|
+
try context.compatibilityAssert(
|
|
213
|
+
otherItems.first(Fill.self) == nil,
|
|
214
|
+
"""
|
|
215
|
+
The Core Animation rendering engine doesn't currently support applying
|
|
216
|
+
trims to filled shapes (only stroked shapes).
|
|
217
|
+
""")
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
try shapeLayer.addAnimations(for: shape.item, context: context.for(shape), pathMultiplier: trimPathMultiplier ?? 1)
|
|
221
|
+
|
|
222
|
+
if let (fill, context) = otherItems.first(Fill.self, context: context) {
|
|
223
|
+
try shapeLayer.addAnimations(for: fill, context: context)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if let (stroke, context) = otherItems.first(Stroke.self, context: context) {
|
|
227
|
+
try shapeLayer.addStrokeAnimations(for: stroke, context: context)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private func setupGradientFillAnimations(
|
|
232
|
+
layers: GradientLayers,
|
|
233
|
+
context: LayerAnimationContext)
|
|
234
|
+
throws
|
|
235
|
+
{
|
|
236
|
+
try layers.shapeMaskLayer.addAnimations(
|
|
237
|
+
for: shape.item,
|
|
238
|
+
context: context.for(shape),
|
|
239
|
+
pathMultiplier: 1)
|
|
240
|
+
|
|
241
|
+
if let (gradientFill, context) = otherItems.first(GradientFill.self, context: context) {
|
|
242
|
+
layers.shapeMaskLayer.fillRule = gradientFill.fillRule.caFillRule
|
|
243
|
+
try layers.gradientColorLayer.addGradientAnimations(for: gradientFill, type: .rgb, context: context)
|
|
244
|
+
try layers.gradientAlphaLayer?.addGradientAnimations(for: gradientFill, type: .alpha, context: context)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private func setupGradientStrokeAnimations(
|
|
249
|
+
layers: GradientLayers,
|
|
250
|
+
context: LayerAnimationContext)
|
|
251
|
+
throws
|
|
252
|
+
{
|
|
253
|
+
var trimPathMultiplier: PathMultiplier? = nil
|
|
254
|
+
if let (trim, context) = otherItems.first(Trim.self, context: context) {
|
|
255
|
+
trimPathMultiplier = try layers.shapeMaskLayer.addAnimations(for: trim, context: context)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
try layers.shapeMaskLayer.addAnimations(
|
|
259
|
+
for: shape.item,
|
|
260
|
+
context: context.for(shape),
|
|
261
|
+
pathMultiplier: trimPathMultiplier ?? 1)
|
|
262
|
+
|
|
263
|
+
if let (gradientStroke, context) = otherItems.first(GradientStroke.self, context: context) {
|
|
264
|
+
try layers.gradientColorLayer.addGradientAnimations(for: gradientStroke, type: .rgb, context: context)
|
|
265
|
+
try layers.gradientAlphaLayer?.addGradientAnimations(for: gradientStroke, type: .alpha, context: context)
|
|
266
|
+
|
|
267
|
+
try layers.shapeMaskLayer.addStrokeAnimations(for: gradientStroke, context: context)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// MARK: - [ShapeItem] helpers
|
|
274
|
+
|
|
275
|
+
extension Array where Element == ShapeItemLayer.Item {
|
|
276
|
+
/// The first `ShapeItem` in this array of the given type
|
|
277
|
+
func first<ItemType: ShapeItem>(
|
|
278
|
+
_: ItemType.Type, context: LayerAnimationContext)
|
|
279
|
+
-> (item: ItemType, context: LayerAnimationContext)?
|
|
280
|
+
{
|
|
281
|
+
for item in self {
|
|
282
|
+
if let match = item.item as? ItemType {
|
|
283
|
+
return (match, context.for(item))
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return nil
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/// The first `ShapeItem` in this array of the given type
|
|
291
|
+
func first<ItemType: ShapeItem>(_: ItemType.Type) -> ItemType? {
|
|
292
|
+
for item in self {
|
|
293
|
+
if let match = item.item as? ItemType {
|
|
294
|
+
return match
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return nil
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
extension LayerAnimationContext {
|
|
303
|
+
/// An updated `LayerAnimationContext` with the`AnimationKeypath`
|
|
304
|
+
/// that refers to this specific `ShapeItem`.
|
|
305
|
+
func `for`(_ item: ShapeItemLayer.Item) -> LayerAnimationContext {
|
|
306
|
+
var context = self
|
|
307
|
+
|
|
308
|
+
for parentGroupName in item.groupPath {
|
|
309
|
+
context.currentKeypath.keys.append(parentGroupName)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
context.currentKeypath.keys.append(item.item.name)
|
|
313
|
+
return context
|
|
314
|
+
}
|
|
315
|
+
}
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
// Created by Cal Stephens on 12/14/21.
|
|
2
|
+
// Copyright © 2021 Airbnb Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
import QuartzCore
|
|
5
|
+
|
|
6
|
+
// MARK: - ShapeLayer
|
|
7
|
+
|
|
8
|
+
/// The CALayer type responsible for rendering `ShapeLayerModel`s
|
|
9
|
+
final class ShapeLayer: BaseCompositionLayer {
|
|
10
|
+
|
|
11
|
+
// MARK: Lifecycle
|
|
12
|
+
|
|
13
|
+
init(shapeLayer: ShapeLayerModel, context: LayerContext) throws {
|
|
14
|
+
self.shapeLayer = shapeLayer
|
|
15
|
+
super.init(layerModel: shapeLayer)
|
|
16
|
+
try setUpGroups(context: context)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
required init?(coder _: NSCoder) {
|
|
20
|
+
fatalError("init(coder:) has not been implemented")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Called by CoreAnimation to create a shadow copy of this layer
|
|
24
|
+
/// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init
|
|
25
|
+
override init(layer: Any) {
|
|
26
|
+
guard let typedLayer = layer as? Self else {
|
|
27
|
+
fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))")
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
shapeLayer = typedLayer.shapeLayer
|
|
31
|
+
super.init(layer: typedLayer)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// MARK: Private
|
|
35
|
+
|
|
36
|
+
private let shapeLayer: ShapeLayerModel
|
|
37
|
+
|
|
38
|
+
private func setUpGroups(context: LayerContext) throws {
|
|
39
|
+
// If the layer has a `Repeater`, the `Group`s are duplicated and offset
|
|
40
|
+
// based on the copy count of the repeater.
|
|
41
|
+
if let repeater = shapeLayer.items.first(where: { $0 is Repeater }) as? Repeater {
|
|
42
|
+
try setUpRepeater(repeater, context: context)
|
|
43
|
+
} else {
|
|
44
|
+
try setupGroups(from: shapeLayer.items, parentGroup: nil, parentGroupPath: [], context: context)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private func setUpRepeater(_ repeater: Repeater, context: LayerContext) throws {
|
|
49
|
+
let items = shapeLayer.items.filter { !($0 is Repeater) }
|
|
50
|
+
let copyCount = Int(try repeater.copies.exactlyOneKeyframe(context: context, description: "repeater copies").value)
|
|
51
|
+
|
|
52
|
+
for index in 0..<copyCount {
|
|
53
|
+
for groupLayer in try makeGroupLayers(from: items, parentGroup: nil, parentGroupPath: [], context: context) {
|
|
54
|
+
let repeatedLayer = RepeaterLayer(repeater: repeater, childLayer: groupLayer, index: index)
|
|
55
|
+
addSublayer(repeatedLayer)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// MARK: - GroupLayer
|
|
63
|
+
|
|
64
|
+
/// The CALayer type responsible for rendering `Group`s
|
|
65
|
+
final class GroupLayer: BaseAnimationLayer {
|
|
66
|
+
|
|
67
|
+
// MARK: Lifecycle
|
|
68
|
+
|
|
69
|
+
init(group: Group, items: [ShapeItemLayer.Item], groupPath: [String], context: LayerContext) throws {
|
|
70
|
+
self.group = group
|
|
71
|
+
self.items = items
|
|
72
|
+
self.groupPath = groupPath
|
|
73
|
+
super.init()
|
|
74
|
+
try setupLayerHierarchy(context: context)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
required init?(coder _: NSCoder) {
|
|
78
|
+
fatalError("init(coder:) has not been implemented")
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Called by CoreAnimation to create a shadow copy of this layer
|
|
82
|
+
/// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init
|
|
83
|
+
override init(layer: Any) {
|
|
84
|
+
guard let typedLayer = layer as? Self else {
|
|
85
|
+
fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))")
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
group = typedLayer.group
|
|
89
|
+
items = typedLayer.items
|
|
90
|
+
groupPath = typedLayer.groupPath
|
|
91
|
+
super.init(layer: typedLayer)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// MARK: Internal
|
|
95
|
+
|
|
96
|
+
override func setupAnimations(context: LayerAnimationContext) throws {
|
|
97
|
+
try super.setupAnimations(context: context)
|
|
98
|
+
|
|
99
|
+
if let (shapeTransform, context) = nonGroupItems.first(ShapeTransform.self, context: context) {
|
|
100
|
+
try addTransformAnimations(for: shapeTransform, context: context)
|
|
101
|
+
try addOpacityAnimation(for: shapeTransform, context: context)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// MARK: Private
|
|
106
|
+
|
|
107
|
+
private let group: Group
|
|
108
|
+
|
|
109
|
+
/// `ShapeItemLayer.Item`s rendered by this `Group`
|
|
110
|
+
/// - In the original `ShapeLayer` data model, these items could have originated from a different group
|
|
111
|
+
private let items: [ShapeItemLayer.Item]
|
|
112
|
+
|
|
113
|
+
/// The keypath that represents this group, with respect to the parent `ShapeLayer`
|
|
114
|
+
/// - Due to the way `GroupLayer`s are setup, the original `ShapeItem`
|
|
115
|
+
/// hierarchy from the `ShapeLayer` data model may no longer exactly
|
|
116
|
+
/// match the hierarchy of `GroupLayer` / `ShapeItemLayer`s constructed
|
|
117
|
+
/// at runtime. Since animation keypaths need to match the original
|
|
118
|
+
/// structure of the `ShapeLayer` data model, we track that info here.
|
|
119
|
+
private let groupPath: [String]
|
|
120
|
+
|
|
121
|
+
/// `ShapeItem`s (other than nested `Group`s) that are rendered by this layer
|
|
122
|
+
private lazy var nonGroupItems = items.filter { !($0.item is Group) }
|
|
123
|
+
|
|
124
|
+
private func setupLayerHierarchy(context: LayerContext) throws {
|
|
125
|
+
// Groups can contain other groups, so we may have to continue
|
|
126
|
+
// recursively creating more `GroupLayer`s
|
|
127
|
+
try setupGroups(from: group.items, parentGroup: group, parentGroupPath: groupPath, context: context)
|
|
128
|
+
|
|
129
|
+
// Create `ShapeItemLayer`s for each subgroup of shapes that should be rendered as a single unit
|
|
130
|
+
// - These groups are listed from front-to-back, so we have to add the sublayers in reverse order
|
|
131
|
+
for shapeRenderGroup in nonGroupItems.shapeRenderGroups.reversed() {
|
|
132
|
+
// When there are multiple path-drawing items, they're supposed to be rendered
|
|
133
|
+
// in a single `CAShapeLayer` (instead of rendering them in separate layers) so
|
|
134
|
+
// `CAShapeLayerFillRule.evenOdd` can be applied correctly if the paths overlap.
|
|
135
|
+
// Since a `CAShapeLayer` only supports animating a single `CGPath` from a single `KeyframeGroup<BezierPath>`,
|
|
136
|
+
// this requires combining all of the path-drawing items into a single set of keyframes.
|
|
137
|
+
if
|
|
138
|
+
shapeRenderGroup.pathItems.count > 1,
|
|
139
|
+
// We currently only support this codepath for `Shape` items that directly contain bezier path keyframes.
|
|
140
|
+
// We could also support this for other path types like rectangles, ellipses, and polygons with more work.
|
|
141
|
+
shapeRenderGroup.pathItems.allSatisfy({ $0.item is Shape }),
|
|
142
|
+
// `Trim`s are currently only applied correctly using individual `ShapeItemLayer`s,
|
|
143
|
+
// because each path has to be trimmed separately.
|
|
144
|
+
!shapeRenderGroup.otherItems.contains(where: { $0.item is Trim })
|
|
145
|
+
{
|
|
146
|
+
let allPathKeyframes = shapeRenderGroup.pathItems.compactMap { ($0.item as? Shape)?.path }
|
|
147
|
+
let combinedShape: CombinedShapeItem
|
|
148
|
+
|
|
149
|
+
// If all of the path-drawing `ShapeItem`s have keyframes with the same timing information,
|
|
150
|
+
// we can combine the `[KeyframeGroup<BezierPath>]` (which have to animate in separate layers)
|
|
151
|
+
// into a single `KeyframeGroup<[BezierPath]>`, which can be combined into a single CGPath animation.
|
|
152
|
+
if let combinedShapeKeyframes = Keyframes.combinedIfPossible(allPathKeyframes) {
|
|
153
|
+
combinedShape = CombinedShapeItem(shapes: combinedShapeKeyframes, name: group.name)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Otherwise, in order for the path fills to be rendered correctly, we have to manually
|
|
157
|
+
// interpolate the path for each shape at each frame ahead of time so we can combine them
|
|
158
|
+
// into a single set of bezier path keyframes.
|
|
159
|
+
else {
|
|
160
|
+
combinedShape = .manuallyInterpolating(shapes: allPathKeyframes, name: group.name, context: context)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let sublayer = try ShapeItemLayer(
|
|
164
|
+
shape: ShapeItemLayer.Item(item: combinedShape, groupPath: shapeRenderGroup.pathItems[0].groupPath),
|
|
165
|
+
otherItems: shapeRenderGroup.otherItems,
|
|
166
|
+
context: context)
|
|
167
|
+
|
|
168
|
+
addSublayer(sublayer)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Otherwise, if each `ShapeItem` that draws a `GGPath` animates independently,
|
|
172
|
+
// we have to create a separate `ShapeItemLayer` for each one. This may render
|
|
173
|
+
// incorrectly if there are multiple paths that overlap with each other.
|
|
174
|
+
else {
|
|
175
|
+
for pathDrawingItem in shapeRenderGroup.pathItems {
|
|
176
|
+
let sublayer = try ShapeItemLayer(
|
|
177
|
+
shape: pathDrawingItem,
|
|
178
|
+
otherItems: shapeRenderGroup.otherItems,
|
|
179
|
+
context: context)
|
|
180
|
+
|
|
181
|
+
addSublayer(sublayer)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
extension CALayer {
|
|
190
|
+
/// Sets up `GroupLayer`s for each `Group` in the given list of `ShapeItem`s
|
|
191
|
+
/// - Each `Group` item becomes its own `GroupLayer` sublayer.
|
|
192
|
+
/// - Other `ShapeItem` are applied to all sublayers
|
|
193
|
+
fileprivate func setupGroups(
|
|
194
|
+
from items: [ShapeItem],
|
|
195
|
+
parentGroup: Group?,
|
|
196
|
+
parentGroupPath: [String],
|
|
197
|
+
context: LayerContext) throws
|
|
198
|
+
{
|
|
199
|
+
let groupLayers = try makeGroupLayers(
|
|
200
|
+
from: items,
|
|
201
|
+
parentGroup: parentGroup,
|
|
202
|
+
parentGroupPath: parentGroupPath,
|
|
203
|
+
context: context)
|
|
204
|
+
|
|
205
|
+
for groupLayer in groupLayers {
|
|
206
|
+
addSublayer(groupLayer)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/// Creates a `GroupLayer` for each `Group` in the given list of `ShapeItem`s
|
|
211
|
+
/// - Each `Group` item becomes its own `GroupLayer` sublayer.
|
|
212
|
+
/// - Other `ShapeItem` are applied to all sublayers
|
|
213
|
+
fileprivate func makeGroupLayers(
|
|
214
|
+
from items: [ShapeItem],
|
|
215
|
+
parentGroup: Group?,
|
|
216
|
+
parentGroupPath: [String],
|
|
217
|
+
context: LayerContext) throws
|
|
218
|
+
-> [GroupLayer]
|
|
219
|
+
{
|
|
220
|
+
var (groupItems, otherItems) = items
|
|
221
|
+
.filter { !$0.hidden }
|
|
222
|
+
.grouped(by: { $0 is Group })
|
|
223
|
+
|
|
224
|
+
// If this shape doesn't have any groups but just has top-level shape items,
|
|
225
|
+
// we can create a placeholder group with those items. (Otherwise the shape items
|
|
226
|
+
// would be silently ignored, since we expect all shape layers to have a top-level group).
|
|
227
|
+
if groupItems.isEmpty, parentGroup == nil {
|
|
228
|
+
groupItems = [Group(items: otherItems, name: "")]
|
|
229
|
+
otherItems = []
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// `ShapeItem`s either draw a path, or modify how a path is rendered.
|
|
233
|
+
// - If this group doesn't have any items that draw a path, then its
|
|
234
|
+
// items are applied to all of this group's children.
|
|
235
|
+
let inheritedItemsForChildGroups: [ShapeItemLayer.Item]
|
|
236
|
+
if !otherItems.contains(where: { $0.drawsCGPath }) {
|
|
237
|
+
inheritedItemsForChildGroups = otherItems.map {
|
|
238
|
+
ShapeItemLayer.Item(item: $0, groupPath: parentGroupPath)
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
inheritedItemsForChildGroups = []
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Groups are listed from front to back,
|
|
245
|
+
// but `CALayer.sublayers` are listed from back to front.
|
|
246
|
+
let groupsInZAxisOrder = groupItems.reversed()
|
|
247
|
+
|
|
248
|
+
return try groupsInZAxisOrder.compactMap { group in
|
|
249
|
+
guard let group = group as? Group else { return nil }
|
|
250
|
+
|
|
251
|
+
var pathForChildren = parentGroupPath
|
|
252
|
+
if !group.name.isEmpty {
|
|
253
|
+
pathForChildren.append(group.name)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let childItems = group.items.map {
|
|
257
|
+
ShapeItemLayer.Item(item: $0, groupPath: pathForChildren)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return try GroupLayer(
|
|
261
|
+
group: group,
|
|
262
|
+
items: childItems + inheritedItemsForChildGroups,
|
|
263
|
+
groupPath: pathForChildren,
|
|
264
|
+
context: context)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
extension ShapeItem {
|
|
270
|
+
/// Whether or not this `ShapeItem` is responsible for rendering a `CGPath`
|
|
271
|
+
var drawsCGPath: Bool {
|
|
272
|
+
switch type {
|
|
273
|
+
case .ellipse, .rectangle, .shape, .star:
|
|
274
|
+
return true
|
|
275
|
+
|
|
276
|
+
case .fill, .gradientFill, .group, .gradientStroke, .merge,
|
|
277
|
+
.repeater, .round, .stroke, .trim, .transform, .unknown:
|
|
278
|
+
return false
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/// Whether or not this `ShapeItem` provides a fill for a set of shapes
|
|
283
|
+
var isFill: Bool {
|
|
284
|
+
switch type {
|
|
285
|
+
case .fill, .gradientFill:
|
|
286
|
+
return true
|
|
287
|
+
|
|
288
|
+
case .ellipse, .rectangle, .shape, .star, .group, .gradientStroke,
|
|
289
|
+
.merge, .repeater, .round, .stroke, .trim, .transform, .unknown:
|
|
290
|
+
return false
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/// Whether or not this `ShapeItem` provides a stroke for a set of shapes
|
|
295
|
+
var isStroke: Bool {
|
|
296
|
+
switch type {
|
|
297
|
+
case .stroke, .gradientStroke:
|
|
298
|
+
return true
|
|
299
|
+
|
|
300
|
+
case .ellipse, .rectangle, .shape, .star, .group, .gradientFill,
|
|
301
|
+
.merge, .repeater, .round, .fill, .trim, .transform, .unknown:
|
|
302
|
+
return false
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
extension Collection {
|
|
308
|
+
/// Splits this collection into two groups, based on the given predicate
|
|
309
|
+
func grouped(by predicate: (Element) -> Bool) -> (trueElements: [Element], falseElements: [Element]) {
|
|
310
|
+
var trueElements = [Element]()
|
|
311
|
+
var falseElements = [Element]()
|
|
312
|
+
|
|
313
|
+
for element in self {
|
|
314
|
+
if predicate(element) {
|
|
315
|
+
trueElements.append(element)
|
|
316
|
+
} else {
|
|
317
|
+
falseElements.append(element)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return (trueElements, falseElements)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// MARK: - ShapeRenderGroup
|
|
326
|
+
|
|
327
|
+
/// A group of `ShapeItem`s that should be rendered together as a single unit
|
|
328
|
+
struct ShapeRenderGroup {
|
|
329
|
+
/// The items in this group that render `CGPath`s
|
|
330
|
+
var pathItems: [ShapeItemLayer.Item] = []
|
|
331
|
+
/// Shape items that modify the appearance of the shapes rendered by this group
|
|
332
|
+
var otherItems: [ShapeItemLayer.Item] = []
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
extension Array where Element == ShapeItemLayer.Item {
|
|
336
|
+
/// Splits this list of `ShapeItem`s into groups that should be rendered together as individual units
|
|
337
|
+
var shapeRenderGroups: [ShapeRenderGroup] {
|
|
338
|
+
var renderGroups = [ShapeRenderGroup()]
|
|
339
|
+
|
|
340
|
+
for item in self {
|
|
341
|
+
// `renderGroups` is non-empty, so is guaranteed to have a valid end index
|
|
342
|
+
let lastIndex = renderGroups.indices.last!
|
|
343
|
+
|
|
344
|
+
if item.item.drawsCGPath {
|
|
345
|
+
renderGroups[lastIndex].pathItems.append(item)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// `Fill` items are unique, because they specifically only apply to _previous_ shapes in a `Group`
|
|
349
|
+
// - For example, with [Rectangle, Fill(Red), Circle, Fill(Blue)], the Rectangle should be Red
|
|
350
|
+
// but the Circle should be Blue.
|
|
351
|
+
// - To handle this, we create a new `ShapeRenderGroup` when we encounter a `Fill` item
|
|
352
|
+
else if item.item.isFill {
|
|
353
|
+
renderGroups[lastIndex].otherItems.append(item)
|
|
354
|
+
renderGroups.append(ShapeRenderGroup())
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Other items in the list are applied to all subgroups
|
|
358
|
+
else {
|
|
359
|
+
for index in renderGroups.indices {
|
|
360
|
+
renderGroups[index].otherItems.append(item)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// `Fill` and `Stroke` items have an `alpha` property that can be animated separately,
|
|
366
|
+
// but each layer only has a single `opacity` property, so we have to create
|
|
367
|
+
// separate layers / render groups for each of these if necessary.
|
|
368
|
+
return renderGroups.flatMap { group -> [ShapeRenderGroup] in
|
|
369
|
+
let (strokesAndFills, otherItems) = group.otherItems.grouped(by: { $0.item.isFill || $0.item.isStroke })
|
|
370
|
+
|
|
371
|
+
// However, if all of the strokes / fills have the exact same opacity animation configuration,
|
|
372
|
+
// then we can continue using a single layer / render group.
|
|
373
|
+
let allAlphaAnimationsAreIdentical = strokesAndFills.allSatisfy { item in
|
|
374
|
+
(item.item as? OpacityAnimationModel)?.opacity
|
|
375
|
+
== (strokesAndFills.first?.item as? OpacityAnimationModel)?.opacity
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if allAlphaAnimationsAreIdentical {
|
|
379
|
+
return [group]
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Create a new group for each stroke / fill
|
|
383
|
+
return strokesAndFills.map { strokeOrFill in
|
|
384
|
+
ShapeRenderGroup(
|
|
385
|
+
pathItems: group.pathItems,
|
|
386
|
+
otherItems: [strokeOrFill] + otherItems)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|