Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add spring to transform animation #463

Merged
merged 2 commits into from
Aug 31, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 2 additions & 12 deletions Example/Example/Examples/Animations/EasingView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ class EasingView: MacawView {
let fromStroke = Stroke(fill: Color.black, width: 3)
let toStroke = Stroke(fill: Color.black, width: 1, dashes: [3, 3])

let all = [Easing.ease, Easing.linear, Easing.easeIn, Easing.easeOut, Easing.easeInOut]
let all = [Easing.ease, Easing.linear, Easing.easeIn, Easing.easeOut, Easing.easeInOut, Easing.elasticInOut]

for (i, easing) in all.enumerated() {
let y = Double(150 + i * 75)
let title = EasingView.title(easing: easing)
let title = String(describing: type(of: easing))
let titleText = Text(text: title, align: .mid, place: .move(dx: centerX, dy: y - 45))

let fromCircle = Circle(cx: centerX - 100, cy: y, r: 25).stroke(with: fromStroke)
Expand All @@ -34,14 +34,4 @@ class EasingView: MacawView {
animation = animations.combine().cycle()
super.init(node: circlesNodes.group(), coder: aDecoder)
}

fileprivate static func title(easing: Easing) -> String {
switch easing {
case .ease: return "Ease"
case .linear: return "Linear"
case .easeIn: return "Ease In"
case .easeOut: return "Ease Out"
case .easeInOut: return "Ease InOut"
}
}
}
12 changes: 6 additions & 6 deletions Macaw.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
57614B0A1F83D15600875933 /* DoubleInterpolation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E0EC1E3B393900D1CB28 /* DoubleInterpolation.swift */; };
57614B0B1F83D15600875933 /* PathSegmentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E12F1E3B393900D1CB28 /* PathSegmentType.swift */; };
57614B0C1F83D15600875933 /* AnimatableVariable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E0E21E3B393900D1CB28 /* AnimatableVariable.swift */; };
57614B0D1F83D15600875933 /* TimingFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E0FD1E3B393900D1CB28 /* TimingFunction.swift */; };
57614B0E1F83D15600875933 /* AnimationCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E0F71E3B393900D1CB28 /* AnimationCache.swift */; };
57614B0F1F83D15600875933 /* Transform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1361E3B393900D1CB28 /* Transform.swift */; };
57614B101F83D15600875933 /* Graphics_macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = A718CD4A1F45C28F00966E06 /* Graphics_macOS.swift */; };
Expand Down Expand Up @@ -157,7 +156,6 @@
57E5E1661E3B393900D1CB28 /* TransformHashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E0FA1E3B393900D1CB28 /* TransformHashable.swift */; };
57E5E1671E3B393900D1CB28 /* MorphingGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E0FB1E3B393900D1CB28 /* MorphingGenerator.swift */; };
57E5E1681E3B393900D1CB28 /* OpacityGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E0FC1E3B393900D1CB28 /* OpacityGenerator.swift */; };
57E5E1691E3B393900D1CB28 /* TimingFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E0FD1E3B393900D1CB28 /* TimingFunction.swift */; };
57E5E16A1E3B393900D1CB28 /* TransformGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E0FE1E3B393900D1CB28 /* TransformGenerator.swift */; };
57E5E16B1E3B393900D1CB28 /* AnimationSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E0FF1E3B393900D1CB28 /* AnimationSequence.swift */; };
57E5E16C1E3B393900D1CB28 /* CombineAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1001E3B393900D1CB28 /* CombineAnimation.swift */; };
Expand Down Expand Up @@ -450,6 +448,8 @@
5B6E194220AC58F900454E7E /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6E191F20AC58F900454E7E /* Color.swift */; };
5B6E194320AC58F900454E7E /* Gradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6E192020AC58F900454E7E /* Gradient.swift */; };
5B6E194420AC58F900454E7E /* Gradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6E192020AC58F900454E7E /* Gradient.swift */; };
5B7D7ED321300D4A00B5ED00 /* TimingFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B7D7ED221300D4A00B5ED00 /* TimingFunction.swift */; };
5B7D7ED421300D4A00B5ED00 /* TimingFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B7D7ED221300D4A00B5ED00 /* TimingFunction.swift */; };
5B7E79CE20CBE69700C50BCF /* masking-path-02-b-manual.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5B7E79CC20CBE69600C50BCF /* masking-path-02-b-manual.reference */; };
5B7E79CF20CBE69700C50BCF /* masking-path-02-b-manual.svg in Resources */ = {isa = PBXBuildFile; fileRef = 5B7E79CD20CBE69700C50BCF /* masking-path-02-b-manual.svg */; };
5B7E79DE20D2781A00C50BCF /* masking-intro-01-f-manual.reference in Resources */ = {isa = PBXBuildFile; fileRef = 5B7E79DC20D2781A00C50BCF /* masking-intro-01-f-manual.reference */; };
Expand Down Expand Up @@ -579,7 +579,6 @@
57E5E0FA1E3B393900D1CB28 /* TransformHashable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformHashable.swift; sourceTree = "<group>"; };
57E5E0FB1E3B393900D1CB28 /* MorphingGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MorphingGenerator.swift; sourceTree = "<group>"; };
57E5E0FC1E3B393900D1CB28 /* OpacityGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpacityGenerator.swift; sourceTree = "<group>"; };
57E5E0FD1E3B393900D1CB28 /* TimingFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimingFunction.swift; sourceTree = "<group>"; };
57E5E0FE1E3B393900D1CB28 /* TransformGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformGenerator.swift; sourceTree = "<group>"; };
57E5E0FF1E3B393900D1CB28 /* AnimationSequence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationSequence.swift; sourceTree = "<group>"; };
57E5E1001E3B393900D1CB28 /* CombineAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CombineAnimation.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -850,6 +849,7 @@
5B6E191E20AC58F900454E7E /* Stroke.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stroke.swift; sourceTree = "<group>"; };
5B6E191F20AC58F900454E7E /* Color.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = "<group>"; };
5B6E192020AC58F900454E7E /* Gradient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Gradient.swift; sourceTree = "<group>"; };
5B7D7ED221300D4A00B5ED00 /* TimingFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimingFunction.swift; sourceTree = "<group>"; };
5B7E79CC20CBE69600C50BCF /* masking-path-02-b-manual.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "masking-path-02-b-manual.reference"; sourceTree = "<group>"; };
5B7E79CD20CBE69700C50BCF /* masking-path-02-b-manual.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "masking-path-02-b-manual.svg"; sourceTree = "<group>"; };
5B7E79DC20D2781A00C50BCF /* masking-intro-01-f-manual.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "masking-intro-01-f-manual.reference"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1087,7 +1087,7 @@
57E5E0FB1E3B393900D1CB28 /* MorphingGenerator.swift */,
57A27BD21E44C5570057BD3A /* ShapeAnimationGenerator.swift */,
57E5E0FC1E3B393900D1CB28 /* OpacityGenerator.swift */,
57E5E0FD1E3B393900D1CB28 /* TimingFunction.swift */,
5B7D7ED221300D4A00B5ED00 /* TimingFunction.swift */,
57E5E0FE1E3B393900D1CB28 /* TransformGenerator.swift */,
);
path = animation_generators;
Expand Down Expand Up @@ -1963,7 +1963,6 @@
57614B0B1F83D15600875933 /* PathSegmentType.swift in Sources */,
57614B0C1F83D15600875933 /* AnimatableVariable.swift in Sources */,
5B6E193C20AC58F900454E7E /* Effect.swift in Sources */,
57614B0D1F83D15600875933 /* TimingFunction.swift in Sources */,
57614B0E1F83D15600875933 /* AnimationCache.swift in Sources */,
57614B0F1F83D15600875933 /* Transform.swift in Sources */,
57614B101F83D15600875933 /* Graphics_macOS.swift in Sources */,
Expand All @@ -1975,6 +1974,7 @@
57614B181F83D15600875933 /* SVGView.swift in Sources */,
57614B191F83D15600875933 /* Arc.swift in Sources */,
57614B1A1F83D15600875933 /* MacawView.swift in Sources */,
5B7D7ED421300D4A00B5ED00 /* TimingFunction.swift in Sources */,
5B6E193420AC58F900454E7E /* Baseline.swift in Sources */,
57614B1B1F83D15600875933 /* Image.swift in Sources */,
57614B1C1F83D15600875933 /* TransformGenerator.swift in Sources */,
Expand Down Expand Up @@ -2096,7 +2096,6 @@
57E5E15B1E3B393900D1CB28 /* DoubleInterpolation.swift in Sources */,
57E5E1961E3B393900D1CB28 /* PathSegmentType.swift in Sources */,
57E5E1531E3B393900D1CB28 /* AnimatableVariable.swift in Sources */,
57E5E1691E3B393900D1CB28 /* TimingFunction.swift in Sources */,
5B6E193B20AC58F900454E7E /* Effect.swift in Sources */,
57E5E1631E3B393900D1CB28 /* AnimationCache.swift in Sources */,
57E5E19D1E3B393900D1CB28 /* Transform.swift in Sources */,
Expand All @@ -2110,6 +2109,7 @@
57E5E18B1E3B393900D1CB28 /* Arc.swift in Sources */,
57E5E1B21E3B393900D1CB28 /* MacawView.swift in Sources */,
57E5E19F1E3B393900D1CB28 /* Image.swift in Sources */,
5B7D7ED321300D4A00B5ED00 /* TimingFunction.swift in Sources */,
5B6E193320AC58F900454E7E /* Baseline.swift in Sources */,
57E5E16A1E3B393900D1CB28 /* TransformGenerator.swift in Sources */,
57E5E1551E3B393900D1CB28 /* AnimationImpl.swift in Sources */,
Expand Down
11 changes: 5 additions & 6 deletions Source/animation/AnimationProducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ class AnimationProducer {
self.addAnimation(next)
}
})

case .opacity:
addOpacityAnimation(animation, sceneLayer: layer, animationCache: cache, completion: {
if let next = animation.next {
Expand Down Expand Up @@ -306,10 +305,10 @@ class AnimationProducer {

let startDate = Date(timeInterval: contentsAnimation.delay, since: Date())

var unionBounds: Rect? = .none
if let startBounds = contentsAnimation.getVFunc()(0.0).group().bounds,
let endBounds = contentsAnimation.getVFunc()(1.0).group().bounds {
unionBounds = startBounds.union(rect: endBounds)
var unionBounds = contentsAnimation.getVFunc()(0.0).group().bounds
stride(from: 0.0, to: 1.0, by: 0.02).forEach { progress in
let t = animation.easing.progressFor(time: progress)
unionBounds = unionBounds?.union(rect: contentsAnimation.getVFunc()(t).group().bounds!)
}

guard let layer = cache?.layerForNode(node, animation: contentsAnimation, customBounds: unionBounds) else {
Expand Down Expand Up @@ -379,7 +378,7 @@ class AnimationProducer {
continue
}

let t = progressForTimingFunction(animation.easing, progress: progress)
let t = animation.easing.progressFor(time: progress)
group.contents = animation.getVFunc()(t)
animation.onProgressUpdate?(progress)

Expand Down
78 changes: 72 additions & 6 deletions Source/animation/Easing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,76 @@
//
//

public enum Easing {
case ease
case linear
case easeIn
case easeOut
case easeInOut
#if os(iOS)
import UIKit
#elseif os(OSX)
import AppKit
#endif

open class Easing {

public static let ease: Easing = Easing()
public static let linear: Easing = Easing()
public static let easeIn: Easing = EaseIn()
public static let easeOut: Easing = EaseOut()
public static let easeInOut: Easing = EaseInOut()
public static let elasticInOut: Easing = ElasticInOut()
public static func elasticInOut(elasticity: Double = 10.0) -> ElasticInOut {
return ElasticInOut(elasticity: elasticity)
}

open func progressFor(time: Double) -> Double {
return time
}
}

private class EaseIn: Easing {
override open func progressFor(time t: Double) -> Double {
return t * t
}
}

private class EaseOut: Easing {
override open func progressFor(time t: Double) -> Double {
return -(t * (t - 2))
}
}

private class EaseInOut: Easing {
override open func progressFor(time t: Double) -> Double {
if t < 0.5 {
return 2.0 * t * t
} else {
return -2.0 * t * t + 4.0 * t - 1.0
}
}
}

public class ElasticInOut: Easing {
let elasticity: Double

init(elasticity: Double = 10.0) { // less elasticity means more springy effect
self.elasticity = elasticity
}

override open func progressFor(time: Double) -> Double {
if time == 0 {
return 0
}
var t = time / 0.5
if t == 2 {
return 1
}
let p = 0.3 * 1.5
let s = p / 4

if t < 1 {
t -= 1
let postFix = pow(2, elasticity * t)
return (-0.5 * (postFix * sin((t - s) * (2 * .pi) / p)))
}
t -= 1
let postFix = pow(2, -elasticity * t)
return (postFix * sin((t - s) * (2 * .pi) / p ) * 0.5 + 1)
}
}
10 changes: 10 additions & 0 deletions Source/animation/types/ContentsAnimation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public extension AnimatableVariable where T: ContentsInterpolation {
return ContentsAnimation(animatedGroup: group, valueFunc: f, animationDuration: during, delay: delay, autostart: false)
}

public func animation(during: Double = 1.0, delay: Double = 0.0, _ f: @escaping ((Double) -> [Node])) -> Animation {
let group = node! as! Group
return ContentsAnimation(animatedGroup: group, valueFunc: f, animationDuration: during, delay: delay, autostart: false)
}

public func animate(_ f: @escaping (Double) -> [Node]) {
let group = node! as! Group
_ = ContentsAnimation(animatedGroup: group, valueFunc: f, animationDuration: 1.0, delay: 0.0, autostart: true)
Expand All @@ -60,4 +65,9 @@ public extension AnimatableVariable where T: ContentsInterpolation {
let group = node! as! Group
_ = ContentsAnimation(animatedGroup: group, valueFunc: f, animationDuration: during, delay: delay, autostart: true)
}

public func animate(during: Double = 1.0, delay: Double = 0.0, _ f: @escaping ((Double) -> [Node])) {
let group = node! as! Group
_ = ContentsAnimation(animatedGroup: group, valueFunc: f, animationDuration: during, delay: delay, autostart: true)
}
}
26 changes: 26 additions & 0 deletions Source/animation/types/TransformAnimation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import Foundation

internal class TransformAnimation: AnimationImpl<Transform> {

var trajectory: Path?

convenience init(animatedNode: Node, startValue: Transform, finalValue: Transform, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {

let interpolationFunc = { (t: Double) -> Transform in
Expand Down Expand Up @@ -31,6 +33,17 @@ internal class TransformAnimation: AnimationImpl<Transform> {
}
}

init(animatedNode: Node, factory: @escaping (() -> ((Double) -> Transform)), along path: Path, animationDuration: Double = 1.0, delay: Double = 0.0, autostart: Bool = false) {
super.init(observableValue: animatedNode.placeVar, factory: factory, animationDuration: animationDuration, delay: delay)
type = .affineTransformation
nodeId = animatedNode.id
self.trajectory = path

if autostart {
self.play()
}
}

open override func reverse() -> Animation {

let factory = { () -> (Double) -> Transform in
Expand Down Expand Up @@ -70,6 +83,11 @@ public extension AnimatableVariable where T: TransformInterpolation {
animation.play()
}

public func animate(along path: Path, during: Double = 1.0, delay: Double = 0.0) {
let animation = self.animation(along: path, during: during, delay: delay)
animation.play()
}

public func animation(from: Transform? = nil, to: Transform, during: Double = 1.0, delay: Double = 0.0) -> Animation {
if let safeFrom = from {
return self.animation((safeFrom >> to).t(during, delay: delay))
Expand Down Expand Up @@ -115,4 +133,12 @@ public extension AnimatableVariable where T: TransformInterpolation {
return TransformAnimation(animatedNode: self.node!, factory: factory, animationDuration: during, delay: delay)
}

public func animation(along path: Path, during: Double = 1.0, delay: Double = 0.0) -> Animation {

let factory = { () -> (Double) -> Transform in
return { (t: Double) in return Transform.identity }
}
return TransformAnimation(animatedNode: self.node!, factory: factory, along: path, animationDuration: during, delay: delay)
}

}
38 changes: 10 additions & 28 deletions Source/animation/types/animation_generators/TimingFunction.swift
Original file line number Diff line number Diff line change
@@ -1,43 +1,25 @@
import Foundation

#if os(iOS)
import UIKit
#elseif os(OSX)
import AppKit
#endif

func caTimingFunction(_ easing: Easing) -> CAMediaTimingFunction {
switch easing {
case .ease:
if easing === Easing.ease {
return CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault)
case .linear:
}
if easing === Easing.linear {
return CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
case .easeIn:
}
if easing === Easing.easeIn {
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
case .easeOut:
}
if easing === Easing.easeOut {
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
case .easeInOut:
}
if easing === Easing.easeInOut {
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
}
return CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
}

func progressForTimingFunction(_ easing: Easing, progress: Double) -> Double {
let t = progress

switch easing {
case .ease:
return t
case .linear:
return t
case .easeIn:
return t * t
case .easeOut:
return -(t * (t - 2))
case .easeInOut:
if t < 0.5 {
return 2.0 * t * t
} else {
return -2.0 * t * t + 4.0 * t - 1.0
}
}
}
Loading