1
1
mirror of https://github.com/exyte/Macaw.git synced 2024-08-15 16:10:39 +03:00

Add path animation

This commit is contained in:
Alisa Mylnikova 2020-06-02 15:49:17 +07:00
parent b13d043073
commit 517e33e2cb
12 changed files with 342 additions and 68 deletions

View File

@ -17,6 +17,7 @@
57AF398C1E67E9DB00F0BFE2 /* EventsExampleController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57AF398B1E67E9DB00F0BFE2 /* EventsExampleController.swift */; };
58E4D50C1D841C6E00EC8815 /* TransformExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E4D50B1D841C6E00EC8815 /* TransformExampleView.swift */; };
5B195EAD2276D5C40008AE8B /* AnimationsHierarchyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B195EAB2276D5C40008AE8B /* AnimationsHierarchyViewController.swift */; };
5B5B393B2481094200753058 /* PathAnimationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5B393A2481094200753058 /* PathAnimationView.swift */; };
5BAE3CB120C54E3D006BEF51 /* FiltersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAE3CB020C54E3D006BEF51 /* FiltersViewController.swift */; };
66AE19DB1CC8CB3C00B78B5E /* tiger.svg in Resources */ = {isa = PBXBuildFile; fileRef = 66AE19DA1CC8CB3C00B78B5E /* tiger.svg */; };
B02E75F11C16104900D1971D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B02E75F01C16104900D1971D /* AppDelegate.swift */; };
@ -52,6 +53,7 @@
57AF398B1E67E9DB00F0BFE2 /* EventsExampleController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventsExampleController.swift; sourceTree = "<group>"; };
58E4D50B1D841C6E00EC8815 /* TransformExampleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformExampleView.swift; sourceTree = "<group>"; };
5B195EAB2276D5C40008AE8B /* AnimationsHierarchyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationsHierarchyViewController.swift; sourceTree = "<group>"; };
5B5B393A2481094200753058 /* PathAnimationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathAnimationView.swift; sourceTree = "<group>"; };
5BAE3CB020C54E3D006BEF51 /* FiltersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersViewController.swift; sourceTree = "<group>"; };
66AE19DA1CC8CB3C00B78B5E /* tiger.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = tiger.svg; path = Example/Assets/SVG/tiger.svg; sourceTree = "<group>"; };
B02E75ED1C16104900D1971D /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -105,6 +107,7 @@
574EC4271CB7DE7F0063F317 /* Examples */ = {
isa = PBXGroup;
children = (
5B5B39392481092A00753058 /* PathAnimation */,
49265C7E2227B1B200923A66 /* Text */,
574EC4331CB7DE7F0063F317 /* Shapes */,
6699B7CE1DFFE8B90072585E /* Transform */,
@ -153,6 +156,14 @@
path = Easing;
sourceTree = "<group>";
};
5B5B39392481092A00753058 /* PathAnimation */ = {
isa = PBXGroup;
children = (
5B5B393A2481094200753058 /* PathAnimationView.swift */,
);
path = PathAnimation;
sourceTree = "<group>";
};
5BAE3CAF20C54DA5006BEF51 /* Filters */ = {
isa = PBXGroup;
children = (
@ -374,6 +385,7 @@
5B195EAD2276D5C40008AE8B /* AnimationsHierarchyViewController.swift in Sources */,
58E4D50C1D841C6E00EC8815 /* TransformExampleView.swift in Sources */,
B02E75F11C16104900D1971D /* AppDelegate.swift in Sources */,
5B5B393B2481094200753058 /* PathAnimationView.swift in Sources */,
5BAE3CB120C54E3D006BEF51 /* FiltersViewController.swift in Sources */,
57AF398C1E67E9DB00F0BFE2 /* EventsExampleController.swift in Sources */,
B04416FA1E041A420016BC50 /* EasingView.swift in Sources */,

View File

@ -1,11 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="pJi-Pa-uLB">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="pJi-Pa-uLB">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@ -14,7 +12,7 @@
<objects>
<navigationController title="Main Navigation Controller" id="pJi-Pa-uLB" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" id="YMh-RQ-7Uo">
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
@ -38,10 +36,10 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="PrB-pf-YOR">
<rect key="frame" x="0.0" y="64" width="375" height="603"/>
<rect key="frame" x="0.0" y="44" width="375" height="623"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<view key="tableFooterView" contentMode="scaleToFill" id="05I-vn-LhR">
<rect key="frame" x="0.0" y="72" width="375" height="44"/>
<rect key="frame" x="0.0" y="100" width="375" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</view>
@ -50,7 +48,7 @@
<rect key="frame" x="0.0" y="28" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="6q0-BY-xeB" id="CU1-RT-JgB">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
</tableViewCell>
@ -91,7 +89,7 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="STd-gH-3Yz" customClass="AnimationsView" customModule="Example" customModuleProvider="target">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="1cy-cc-TWk">
@ -174,7 +172,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleAspectFit" translatesAutoresizingMaskIntoConstraints="NO" id="yHP-DJ-SIl" customClass="SVGView" customModule="Macaw">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="fileName" value="tiger"/>
@ -207,7 +205,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9JG-dg-RFE" customClass="MacawView" customModule="Macaw">
<rect key="frame" x="0.0" y="64" width="375" height="603"/>
<rect key="frame" x="0.0" y="44" width="375" height="623"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</view>
</subviews>
@ -241,7 +239,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7vb-6r-3PQ" customClass="MorphingView" customModule="Example" customModuleProvider="target">
<rect key="frame" x="0.0" y="128" width="375" height="539"/>
<rect key="frame" x="0.0" y="108" width="375" height="559"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</view>
</subviews>
@ -272,7 +270,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sIX-jt-2uP" customClass="ShapesExampleView" customModule="Example" customModuleProvider="target">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</view>
</subviews>
@ -287,7 +285,7 @@
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-143" y="470"/>
<point key="canvasLocation" x="630" y="468"/>
</scene>
<!--Transform-->
<scene sceneID="rdc-fb-hVp">
@ -302,7 +300,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="UWr-5G-i0w" customClass="TransformExampleView" customModule="Example" customModuleProvider="target">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</view>
</subviews>
@ -332,7 +330,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleAspectFit" translatesAutoresizingMaskIntoConstraints="NO" id="8gR-K6-HAF" customClass="MacawView" customModule="Macaw">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</view>
</subviews>
@ -366,7 +364,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="hPm-Wd-j2e" customClass="TextsExampleView" customModule="Example" customModuleProvider="target">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</subviews>
@ -396,7 +394,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="WpG-iW-4sM" customClass="MacawView" customModule="Macaw">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</subviews>
@ -416,5 +414,35 @@
</objects>
<point key="canvasLocation" x="598" y="1194"/>
</scene>
<!--PathAnimation-->
<scene sceneID="a9H-hW-qGe">
<objects>
<viewController storyboardIdentifier="PathAnimationViewController" title="PathAnimation" automaticallyAdjustsScrollViewInsets="NO" id="ySx-nz-Rdw" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="d4G-8G-Gj8"/>
<viewControllerLayoutGuide type="bottom" id="e41-7I-vvw"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="iZo-2r-op4">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Bnp-nT-a9e" customClass="PathAnimationView" customModule="Example" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="Bnp-nT-a9e" firstAttribute="leading" secondItem="iZo-2r-op4" secondAttribute="leading" id="P5H-37-Y7J"/>
<constraint firstItem="Bnp-nT-a9e" firstAttribute="top" secondItem="d4G-8G-Gj8" secondAttribute="bottom" id="cUK-Wp-aZO"/>
<constraint firstItem="e41-7I-vvw" firstAttribute="top" secondItem="Bnp-nT-a9e" secondAttribute="bottom" id="wDz-Jc-MFE"/>
<constraint firstAttribute="trailing" secondItem="Bnp-nT-a9e" secondAttribute="trailing" id="zro-AD-Af6"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Iwb-9g-Fir" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-143" y="470"/>
</scene>
</scenes>
</document>

View File

@ -0,0 +1,88 @@
//
// PathAnimationView.swift
// Example
//
// Created by Alisa Mylnikova on 29/05/2020.
// Copyright © 2020 Exyte. All rights reserved.
//
import UIKit
import Macaw
class PathAnimationView: MacawView {
required init?(coder aDecoder: NSCoder) {
super.init(node: Group(), coder: aDecoder)
newScene()
}
func newScene() {
let side = Double(150)
let x = Double(self.bounds.width/2) - side/2
let y = Double(self.bounds.height/2) - side/2
let initialTriangle = Shape(form: makeInitialTriangle(side: side).cgPath.toMacaw(), stroke: Stroke(fill: randomEmeraldColor(), width: 1), place: .move(x, y))
self.node = [initialTriangle].group()
fractalStep(allTriangles: [initialTriangle], currentTier: [initialTriangle], side: side, depth: 0)
}
func fractalStep(allTriangles: [Shape], currentTier: [Shape], side: Double, depth: Int) {
var tierAnimations = [Animation]()
for shape in currentTier {
tierAnimations.append(shape.formVar.appearanceAnimation())
}
tierAnimations.combine().onComplete {
if depth < 4 {
let newTier = self.createTier(parentTier: currentTier, side: side/2)
self.node = (allTriangles + newTier).group()
self.fractalStep(allTriangles: allTriangles + newTier, currentTier: newTier, side: side/2, depth: depth+1)
}
}
.play()
}
func createTier(parentTier: [Shape], side: Double) -> [Shape] {
let a = sqrt(3)/2
let pointLeft = CGPoint(x: 0, y: side*a)
let pointRight = CGPoint(x: side, y: side*a)
let pointUp = CGPoint(x: side/2, y: 0)
let leftTriangle = makeTriangle(pointRight, pointUp, pointLeft)
let rightTriangle = makeTriangle(pointLeft, pointRight, pointUp)
let bottomTriangle = makeTriangle(pointUp, pointLeft, pointRight)
var result: [Shape] = []
for parent in parentTier {
let left = Shape(form: leftTriangle.cgPath.toMacaw(), stroke: Stroke(fill: randomEmeraldColor(), width: 1), place: parent.place.move(-side/2, 0))
let right = Shape(form: rightTriangle.cgPath.toMacaw(), stroke: Stroke(fill: randomEmeraldColor(), width: 1), place: parent.place.move(3*side/2, 0))
let bottom = Shape(form: bottomTriangle.cgPath.toMacaw(), stroke: Stroke(fill: randomEmeraldColor(), width: 1), place: parent.place.move(side/2, side*sqrt(3)))
result.append(contentsOf: [left, right, bottom])
}
return result
}
func makeTriangle(_ point1: CGPoint, _ point2: CGPoint, _ point3: CGPoint) -> MBezierPath {
let path = MBezierPath()
path.move(to: point1)
path.addLine(to: point2)
path.addLine(to: point3)
path.close()
return path
}
func makeInitialTriangle(side: Double) -> MBezierPath {
let a = sqrt(3)/2
let pointLeft = CGPoint(x: 0, y: side*a)
let pointRight = CGPoint(x: side, y: side*a)
let pointUp = CGPoint(x: side/2, y: 0)
return makeTriangle(pointRight, pointUp, pointLeft)
}
func randomEmeraldColor() -> Color {
return Color.rgb(r: Int.random(in: 70...100), g: Int.random(in: 190...220), b: Int.random(in: 110...140))
}
}

View File

@ -14,7 +14,8 @@ open class MenuViewController: UIViewController, UITableViewDataSource, UITableV
"EventsExampleController",
"FiltersViewController",
"TextsViewController",
"AnimationsHierarchyViewController"
"AnimationsHierarchyViewController",
"PathAnimationViewController"
].map {
UIStoryboard(name: "Main", bundle: .none).instantiateViewController(withIdentifier: $0)
}

View File

@ -603,6 +603,8 @@
5BAEA9C9206CEAA20049AAAE /* viewBox.svg in Resources */ = {isa = PBXBuildFile; fileRef = 5BAEA9C8206CEAA20049AAAE /* viewBox.svg */; };
5BC2CA0D21C7B8F500AC46D9 /* CombinationAnimationGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2CA0C21C7B8F400AC46D9 /* CombinationAnimationGenerator.swift */; };
5BC2CA0E21C7B8F900AC46D9 /* CombinationAnimationGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2CA0C21C7B8F400AC46D9 /* CombinationAnimationGenerator.swift */; };
5BECDC7B222FE6DE009C8E6A /* PathAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BECDC7A222FE6DD009C8E6A /* PathAnimation.swift */; };
5BECDC7D222FE6E2009C8E6A /* PathAnimationGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BECDC7C222FE6E2009C8E6A /* PathAnimationGenerator.swift */; };
5BFEF5CE20B80A83008DAC11 /* BlendEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFEF5CC20B80A82008DAC11 /* BlendEffect.swift */; };
5BFEF5CF20B80A83008DAC11 /* BlendEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFEF5CC20B80A82008DAC11 /* BlendEffect.swift */; };
5BFEF5D020B80A83008DAC11 /* ColorMatrixEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFEF5CD20B80A83008DAC11 /* ColorMatrixEffect.swift */; };
@ -1195,6 +1197,8 @@
5BAE2057208F24DE006BF277 /* SceneSerialization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneSerialization.swift; sourceTree = "<group>"; };
5BAEA9C8206CEAA20049AAAE /* viewBox.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = viewBox.svg; sourceTree = "<group>"; };
5BC2CA0C21C7B8F400AC46D9 /* CombinationAnimationGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CombinationAnimationGenerator.swift; sourceTree = "<group>"; };
5BECDC7A222FE6DD009C8E6A /* PathAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PathAnimation.swift; path = Source/animation/types/PathAnimation.swift; sourceTree = SOURCE_ROOT; };
5BECDC7C222FE6E2009C8E6A /* PathAnimationGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PathAnimationGenerator.swift; path = animation_generators/PathAnimationGenerator.swift; sourceTree = "<group>"; };
5BFEF5CC20B80A82008DAC11 /* BlendEffect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlendEffect.swift; sourceTree = "<group>"; };
5BFEF5CD20B80A83008DAC11 /* ColorMatrixEffect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorMatrixEffect.swift; sourceTree = "<group>"; };
5BFEF5D420BC1C1E008DAC11 /* paths-data-18-f-manual.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "paths-data-18-f-manual.svg"; sourceTree = "<group>"; };
@ -1576,6 +1580,7 @@
57E5E1011E3B393900D1CB28 /* ContentsAnimation.swift */,
57E5E1021E3B393900D1CB28 /* MorphingAnimation.swift */,
57E5E1031E3B393900D1CB28 /* OpacityAnimation.swift */,
5BECDC7C222FE6E2009C8E6A /* PathAnimationGenerator.swift */,
57A27BD01E44C5460057BD3A /* ShapeAnimation.swift */,
57E5E1041E3B393900D1CB28 /* TransformAnimation.swift */,
);
@ -1586,12 +1591,13 @@
isa = PBXGroup;
children = (
57E5E0F61E3B393900D1CB28 /* Cache */,
5BC2CA0C21C7B8F400AC46D9 /* CombinationAnimationGenerator.swift */,
57E5E0FB1E3B393900D1CB28 /* MorphingGenerator.swift */,
57A27BD21E44C5570057BD3A /* ShapeAnimationGenerator.swift */,
57E5E0FC1E3B393900D1CB28 /* OpacityGenerator.swift */,
5BECDC7A222FE6DD009C8E6A /* PathAnimation.swift */,
57A27BD21E44C5570057BD3A /* ShapeAnimationGenerator.swift */,
5B7D7ED221300D4A00B5ED00 /* TimingFunction.swift */,
57E5E0FE1E3B393900D1CB28 /* TransformGenerator.swift */,
5BC2CA0C21C7B8F400AC46D9 /* CombinationAnimationGenerator.swift */,
);
path = animation_generators;
sourceTree = "<group>";
@ -2931,6 +2937,7 @@
57E5E1AF1E3B393900D1CB28 /* CAAnimationClosure.swift in Sources */,
5B6E194320AC58F900454E7E /* Gradient.swift in Sources */,
A718CD441F45C28200966E06 /* Common_iOS.swift in Sources */,
5BECDC7D222FE6E2009C8E6A /* PathAnimationGenerator.swift in Sources */,
5B6E194120AC58F900454E7E /* Color.swift in Sources */,
A718CD4D1F45C28F00966E06 /* Common_macOS.swift in Sources */,
57F1087A1F53C92000DC365B /* MDisplayLink.swift in Sources */,
@ -2961,6 +2968,7 @@
5B6E193520AC58F900454E7E /* Stop.swift in Sources */,
572CEFC71E2CED4B008C7C83 /* SWXMLHash+TypeConversion.swift in Sources */,
30FF496B215CF0ED00FF653C /* MCAShapeLayerLineCap_iOS.swift in Sources */,
5BECDC7B222FE6DE009C8E6A /* PathAnimation.swift in Sources */,
57E5E16B1E3B393900D1CB28 /* AnimationSequence.swift in Sources */,
5B1A8C7620A15F7300E5FFAE /* SVGNodeLayout.swift in Sources */,
57E5E1671E3B393900D1CB28 /* MorphingGenerator.swift in Sources */,

View File

@ -17,6 +17,7 @@ enum AnimationType {
case combine
case morphing
case shape
case path
case empty
}

View File

@ -125,6 +125,12 @@ class AnimationProducer {
self.play(next, context)
}
}
case .path:
addPathAnimation(animation, context, sceneLayer: layer) {
if let next = animation.next {
self.play(next, context)
}
}
default:
break
}

View File

@ -0,0 +1,69 @@
//
// PathAnimation.swift
// Macaw
//
// Created by Victor Sukochev on 01/01/2018.
//
import Foundation
class PathAnimation: AnimationImpl<Locus> {
convenience init(animatedNode: Shape, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
let interpolationFunc = { (t: Double) -> Locus in
return animatedNode.form
}
self.init(animatedNode: animatedNode, valueFunc: interpolationFunc, animationDuration: animationDuration, delay: delay, autostart: autostart, fps: fps)
}
init(animatedNode: Shape, valueFunc: @escaping (Double) -> Locus, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
super.init(observableValue: animatedNode.formVar, valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps)
type = .path
node = animatedNode
if autostart {
self.play()
}
}
init(animatedNode: Shape, factory: @escaping (() -> ((Double) -> Locus)), animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
super.init(observableValue: animatedNode.formVar, factory: factory, animationDuration: animationDuration, delay: delay, fps: fps)
type = .path
node = animatedNode
if autostart {
self.play()
}
}
// Pause state not available for discreet animation
override public func pause() {
stop()
}
}
public typealias PathAnimationDescription = AnimationDescription<Locus>
public extension AnimatableVariable where T: LocusInterpolation {
func appearanceAnimation(during: Double = 1.0, delay: Double = 0.0) -> Animation {
return PathAnimation(animatedNode: node as! Shape, animationDuration: during, delay: delay, autostart: false)
}
}
// MARK: - Group
public extension AnimatableVariable where T: ContentsInterpolation {
func appearanceAnimation(during: Double = 1.0, delay: Double = 0.0) -> Animation {
let group = node as! Group
let shapes = group.contents.compactMap { $0 as? Shape }
var animations = shapes.map { $0.formVar.appearanceAnimation(during: during, delay: delay) }
let groups = group.contents.compactMap { $0 as? Group }
let groupAnimations = groups.map({ $0.contentsVar.appearanceAnimation(during: during, delay: delay) })
animations.append(contentsOf: groupAnimations)
return animations.combine(node: node)
}
}

View File

@ -69,27 +69,7 @@ func addMorphingAnimation(_ animation: BasicAnimation, _ context: AnimationConte
}
layer.path = fromLocus.toCGPath()
// Stroke
if let stroke = shape.stroke {
if let color = stroke.fill as? Color {
layer.strokeColor = color.toCG()
} else {
layer.strokeColor = MColor.black.cgColor
}
layer.lineWidth = CGFloat(stroke.width)
layer.lineCap = MCAShapeLayerLineCap.mapToGraphics(model: stroke.cap)
layer.lineJoin = MCAShapeLayerLineJoin.mapToGraphics(model: stroke.join)
layer.lineDashPattern = stroke.dashes.map { NSNumber(value: $0) }
}
// Fill
if let color = shape.fill as? Color {
layer.fillColor = color.toCG()
} else {
layer.fillColor = MColor.clear.cgColor
}
layer.setupStrokeAndFill(shape)
let animationId = animation.ID
layer.add(generatedAnimation, forKey: animationId)

View File

@ -0,0 +1,73 @@
//
// PathAnimationGenerator.swift
// Macaw
//
// Created by Victor Sukochev on 01/01/2018.
//
import Foundation
#if os(iOS)
import UIKit
#elseif os(OSX)
import AppKit
#endif
func addPathAnimation(_ animation: BasicAnimation, _ context: AnimationContext, sceneLayer: CALayer, completion: @escaping (() -> Void)) {
guard let shape = animation.node as? Shape, let renderer = animation.nodeRenderer else {
return
}
let duration = animation.autoreverses ? animation.getDuration() / 2.0 : animation.getDuration()
let layer = AnimationUtils.layerForNodeRenderer(renderer, animation: animation, shouldRenderContent: false)
// Creating proper animation
let generatedAnim = generatePathAnimation(from: 0.0, to: 1.0, duration: duration)
generatedAnim.repeatCount = Float(animation.repeatCount)
generatedAnim.timingFunction = caTimingFunction(animation.easing)
generatedAnim.autoreverses = animation.autoreverses
generatedAnim.progress = { progress in
let t = Double(progress)
animation.progress = t
animation.onProgressUpdate?(t)
}
generatedAnim.completion = { finished in
animation.progress = animation.manualStop ? 0.0 : 1.0
renderer.freeLayer()
if !animation.cycled && !animation.manualStop {
animation.completion?()
}
completion()
}
//layer.path = RenderUtils.toCGPath(shape.form).copy(using: &layer.transform)
layer.path = shape.form.toCGPath()
layer.setupStrokeAndFill(shape)
layer.add(generatedAnim, forKey: animation.ID)
animation.removeFunc = { [weak layer] in
layer?.removeAnimation(forKey: animation.ID)
}
}
fileprivate func generatePathAnimation(from: Double, to: Double, duration: Double) -> CAAnimation {
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = from
animation.toValue = to
animation.duration = duration
animation.fillMode = CAMediaTimingFillMode.forwards
animation.isRemovedOnCompletion = false
return animation
}

View File

@ -76,31 +76,7 @@ func addShapeAnimation(_ animation: BasicAnimation, _ context: AnimationContext,
}
layer.path = fromShape.form.toCGPath()
// Stroke
if let stroke = shape.stroke {
if let color = stroke.fill as? Color {
layer.strokeColor = color.toCG()
} else {
layer.strokeColor = MColor.black.cgColor
}
layer.lineWidth = CGFloat(stroke.width)
layer.lineCap = MCAShapeLayerLineCap.mapToGraphics(model: stroke.cap)
layer.lineJoin = MCAShapeLayerLineJoin.mapToGraphics(model: stroke.join)
layer.lineDashPattern = stroke.dashes.map { NSNumber(value: $0) }
layer.lineDashPhase = CGFloat(stroke.offset)
} else if shape.fill == nil {
layer.strokeColor = MColor.black.cgColor
layer.lineWidth = 1.0
}
// Fill
if let color = shape.fill as? Color {
layer.fillColor = color.toCG()
} else {
layer.fillColor = MColor.clear.cgColor
}
layer.setupStrokeAndFill(fromShape)
let animationId = animation.ID
layer.add(generatedAnimation, forKey: animationId)

View File

@ -20,3 +20,35 @@ class ShapeLayer: CAShapeLayer {
renderer?.directRender(in: ctx, force: isForceRenderingEnabled)
}
}
extension ShapeLayer {
func setupStrokeAndFill(_ shape: Shape) {
// Stroke
if let stroke = shape.stroke {
if let color = stroke.fill as? Color {
strokeColor = color.toCG()
} else {
strokeColor = MColor.black.cgColor
}
lineWidth = CGFloat(stroke.width)
lineCap = MCAShapeLayerLineCap.mapToGraphics(model: stroke.cap)
lineJoin = MCAShapeLayerLineJoin.mapToGraphics(model: stroke.join)
lineDashPattern = stroke.dashes.map { NSNumber(value: $0) }
lineDashPhase = CGFloat(stroke.offset)
} else if shape.fill == nil {
strokeColor = MColor.black.cgColor
lineWidth = 1.0
}
// Fill
if let color = shape.fill as? Color {
fillColor = color.toCG()
} else {
fillColor = MColor.clear.cgColor
}
}
}