From 24c8e0b8f1793d882a7a5195b7917fcac6ee843c Mon Sep 17 00:00:00 2001 From: Alisa Mylnikova Date: Mon, 6 Aug 2018 17:50:47 +0700 Subject: [PATCH 1/2] Add spring to transform animation Add chain function Replace spring with elastic Return value func for transform Remove chain ccee Add elasticity paramenter --- .../Examples/Animations/EasingView.swift | 12 +-- Macaw.xcodeproj/project.pbxproj | 6 -- Source/animation/AnimationProducer.swift | 11 +-- Source/animation/Easing.swift | 97 +++++++++++++++++-- .../animation/types/ContentsAnimation.swift | 10 ++ .../animation/types/TransformAnimation.swift | 26 +++++ .../MorphingGenerator.swift | 2 +- .../OpacityGenerator.swift | 2 +- .../ShapeAnimationGenerator.swift | 2 +- .../animation_generators/TimingFunction.swift | 43 -------- .../TransformGenerator.swift | 58 ++++++----- 11 files changed, 163 insertions(+), 106 deletions(-) delete mode 100644 Source/animation/types/animation_generators/TimingFunction.swift diff --git a/Example/Example/Examples/Animations/EasingView.swift b/Example/Example/Examples/Animations/EasingView.swift index fef9c0d9..098dcd83 100644 --- a/Example/Example/Examples/Animations/EasingView.swift +++ b/Example/Example/Examples/Animations/EasingView.swift @@ -18,7 +18,7 @@ class EasingView: MacawView { 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) @@ -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" - } - } } diff --git a/Macaw.xcodeproj/project.pbxproj b/Macaw.xcodeproj/project.pbxproj index d42b621e..183e1f2f 100644 --- a/Macaw.xcodeproj/project.pbxproj +++ b/Macaw.xcodeproj/project.pbxproj @@ -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 */; }; @@ -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 */; }; @@ -579,7 +577,6 @@ 57E5E0FA1E3B393900D1CB28 /* TransformHashable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformHashable.swift; sourceTree = ""; }; 57E5E0FB1E3B393900D1CB28 /* MorphingGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MorphingGenerator.swift; sourceTree = ""; }; 57E5E0FC1E3B393900D1CB28 /* OpacityGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpacityGenerator.swift; sourceTree = ""; }; - 57E5E0FD1E3B393900D1CB28 /* TimingFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimingFunction.swift; sourceTree = ""; }; 57E5E0FE1E3B393900D1CB28 /* TransformGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformGenerator.swift; sourceTree = ""; }; 57E5E0FF1E3B393900D1CB28 /* AnimationSequence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationSequence.swift; sourceTree = ""; }; 57E5E1001E3B393900D1CB28 /* CombineAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CombineAnimation.swift; sourceTree = ""; }; @@ -1087,7 +1084,6 @@ 57E5E0FB1E3B393900D1CB28 /* MorphingGenerator.swift */, 57A27BD21E44C5570057BD3A /* ShapeAnimationGenerator.swift */, 57E5E0FC1E3B393900D1CB28 /* OpacityGenerator.swift */, - 57E5E0FD1E3B393900D1CB28 /* TimingFunction.swift */, 57E5E0FE1E3B393900D1CB28 /* TransformGenerator.swift */, ); path = animation_generators; @@ -1963,7 +1959,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 */, @@ -2096,7 +2091,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 */, diff --git a/Source/animation/AnimationProducer.swift b/Source/animation/AnimationProducer.swift index 8af248bc..00e3da00 100644 --- a/Source/animation/AnimationProducer.swift +++ b/Source/animation/AnimationProducer.swift @@ -93,7 +93,6 @@ class AnimationProducer { self.addAnimation(next) } }) - case .opacity: addOpacityAnimation(animation, sceneLayer: layer, animationCache: cache, completion: { if let next = animation.next { @@ -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.progressForTimingFunction(progress: progress) + unionBounds = unionBounds?.union(rect: contentsAnimation.getVFunc()(t).group().bounds!) } guard let layer = cache?.layerForNode(node, animation: contentsAnimation, customBounds: unionBounds) else { @@ -379,7 +378,7 @@ class AnimationProducer { continue } - let t = progressForTimingFunction(animation.easing, progress: progress) + let t = animation.easing.progressForTimingFunction(progress: progress) group.contents = animation.getVFunc()(t) animation.onProgressUpdate?(progress) diff --git a/Source/animation/Easing.swift b/Source/animation/Easing.swift index 17b2d5f5..d5354497 100644 --- a/Source/animation/Easing.swift +++ b/Source/animation/Easing.swift @@ -6,10 +6,95 @@ // // -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 = Ease() + 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 caTimingFunction() -> CAMediaTimingFunction { + return CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) + } + + open func progressForTimingFunction(progress: Double) -> Double { + return progress + } +} + +private class Ease: Easing { + override open func caTimingFunction() -> CAMediaTimingFunction { + return CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault) + } +} + +private class EaseIn: Easing { + override open func caTimingFunction() -> CAMediaTimingFunction { + return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) + } + override open func progressForTimingFunction(progress t: Double) -> Double { + return t * t + } +} + +private class EaseOut: Easing { + override open func caTimingFunction() -> CAMediaTimingFunction { + return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) + } + override open func progressForTimingFunction(progress t: Double) -> Double { + return -(t * (t - 2)) + } +} + +private class EaseInOut: Easing { + override open func caTimingFunction() -> CAMediaTimingFunction { + return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + } + override open func progressForTimingFunction(progress 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 progressForTimingFunction(progress: Double) -> Double { + if progress == 0 { + return 0 + } + var t = progress / 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) + } } diff --git a/Source/animation/types/ContentsAnimation.swift b/Source/animation/types/ContentsAnimation.swift index 66a72d46..94296551 100644 --- a/Source/animation/types/ContentsAnimation.swift +++ b/Source/animation/types/ContentsAnimation.swift @@ -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) @@ -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) + } } diff --git a/Source/animation/types/TransformAnimation.swift b/Source/animation/types/TransformAnimation.swift index 6a4a5c86..4730354a 100644 --- a/Source/animation/types/TransformAnimation.swift +++ b/Source/animation/types/TransformAnimation.swift @@ -2,6 +2,8 @@ import Foundation internal class TransformAnimation: AnimationImpl { + 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 @@ -31,6 +33,17 @@ internal class TransformAnimation: AnimationImpl { } } + 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 @@ -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)) @@ -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) + } + } diff --git a/Source/animation/types/animation_generators/MorphingGenerator.swift b/Source/animation/types/animation_generators/MorphingGenerator.swift index bf124123..acc4005c 100644 --- a/Source/animation/types/animation_generators/MorphingGenerator.swift +++ b/Source/animation/types/animation_generators/MorphingGenerator.swift @@ -44,7 +44,7 @@ func addMorphingAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, anim renderTransform: layer.renderTransform!) generatedAnim.repeatCount = Float(animation.repeatCount) - generatedAnim.timingFunction = caTimingFunction(animation.easing) + generatedAnim.timingFunction = animation.easing.caTimingFunction() generatedAnim.autoreverses = animation.autoreverses generatedAnim.completion = { finished in diff --git a/Source/animation/types/animation_generators/OpacityGenerator.swift b/Source/animation/types/animation_generators/OpacityGenerator.swift index 4404d96a..f0fbc6d2 100644 --- a/Source/animation/types/animation_generators/OpacityGenerator.swift +++ b/Source/animation/types/animation_generators/OpacityGenerator.swift @@ -21,7 +21,7 @@ func addOpacityAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, anima offset: animation.pausedProgress, fps: opacityAnimation.logicalFps) generatedAnimation.repeatCount = Float(animation.repeatCount) - generatedAnimation.timingFunction = caTimingFunction(animation.easing) + generatedAnimation.timingFunction = animation.easing.caTimingFunction() generatedAnimation.completion = { finished in diff --git a/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift b/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift index e67a2aaa..147fd692 100644 --- a/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift +++ b/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift @@ -43,7 +43,7 @@ func addShapeAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animati renderTransform: layer.renderTransform!) generatedAnim.repeatCount = Float(animation.repeatCount) - generatedAnim.timingFunction = caTimingFunction(animation.easing) + generatedAnim.timingFunction = animation.easing.caTimingFunction() generatedAnim.autoreverses = animation.autoreverses generatedAnim.completion = { finished in diff --git a/Source/animation/types/animation_generators/TimingFunction.swift b/Source/animation/types/animation_generators/TimingFunction.swift deleted file mode 100644 index b0c6b59e..00000000 --- a/Source/animation/types/animation_generators/TimingFunction.swift +++ /dev/null @@ -1,43 +0,0 @@ -import Foundation - -#if os(iOS) -import UIKit -#elseif os(OSX) -import AppKit -#endif - -func caTimingFunction(_ easing: Easing) -> CAMediaTimingFunction { - switch easing { - case .ease: - return CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault) - case .linear: - return CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) - case .easeIn: - return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) - case .easeOut: - return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) - case .easeInOut: - return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) - } -} - -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 - } - } -} diff --git a/Source/animation/types/animation_generators/TransformGenerator.swift b/Source/animation/types/animation_generators/TransformGenerator.swift index 1e62e15e..876d8b1e 100644 --- a/Source/animation/types/animation_generators/TransformGenerator.swift +++ b/Source/animation/types/animation_generators/TransformGenerator.swift @@ -15,11 +15,17 @@ func addTransformAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, ani return } + if transformAnimation.trajectory != nil && transformAnimation.easing === Easing.elasticInOut { + fatalError("Transform animation with trajectory can't have elastic easing, try using contentVar animation instead") + } + + node.placeVar.value = transformAnimation.getVFunc()(0.0) + // Creating proper animation var generatedAnimation: CAAnimation? - generatedAnimation = transformAnimationByFunc(node, - valueFunc: transformAnimation.getVFunc(), + generatedAnimation = transformAnimationByFunc(transformAnimation, + node: node, duration: animation.getDuration(), offset: animation.pausedProgress, fps: transformAnimation.logicalFps) @@ -29,7 +35,6 @@ func addTransformAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, ani } generatedAnim.repeatCount = Float(animation.repeatCount) - generatedAnim.timingFunction = caTimingFunction(animation.easing) generatedAnim.completion = { finished in @@ -75,36 +80,32 @@ func addTransformAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, ani } } -func transfomToCG(_ transform: Transform) -> CGAffineTransform { - return CGAffineTransform( - a: CGFloat(transform.m11), - b: CGFloat(transform.m12), - c: CGFloat(transform.m21), - d: CGFloat(transform.m22), - tx: CGFloat(transform.dx), - ty: CGFloat(transform.dy)) -} +func transformAnimationByFunc(_ animation: TransformAnimation, node: Node, duration: Double, offset: Double, fps: UInt) -> CAAnimation { -func transformAnimationByFunc(_ node: Node, valueFunc: (Double) -> Transform, duration: Double, offset: Double, fps: UInt) -> CAAnimation { + let valueFunc = animation.getVFunc() + let view = nodesMap.getView(node) - var transformValues = [CATransform3D]() - var timeValues = [Double]() + if let trajectory = animation.trajectory { + let pathAnimation = CAKeyframeAnimation(keyPath: "position") + pathAnimation.timingFunction = animation.easing.caTimingFunction() + pathAnimation.duration = duration / 2 + pathAnimation.autoreverses = animation.autoreverses + let value = AnimationUtils.absoluteTransform(node, pos: valueFunc(0), view: view) + pathAnimation.values = [NSValue(caTransform3D: CATransform3DMakeAffineTransform(value.toCG()))] + pathAnimation.fillMode = kCAFillModeForwards + pathAnimation.isRemovedOnCompletion = false + pathAnimation.path = trajectory.toCGPath() + + return pathAnimation + } + var transformValues = [CATransform3D]() let step = 1.0 / (duration * Double(fps)) - var dt = 0.0 var tValue = Array(stride(from: 0.0, to: 1.0, by: step)) tValue.append(1.0) for t in tValue { - - dt = t - if 1.0 - dt < step { - dt = 1.0 - } - - timeValues.append(dt) - - let view = nodesMap.getView(node) - let value = AnimationUtils.absoluteTransform(node, pos: valueFunc(offset + dt), view: view) + let progress = animation.easing.progressForTimingFunction(progress: t) + let value = AnimationUtils.absoluteTransform(node, pos: valueFunc(offset + progress), view: view) let cgValue = CATransform3DMakeAffineTransform(value.toCG()) transformValues.append(cgValue) } @@ -112,13 +113,8 @@ func transformAnimationByFunc(_ node: Node, valueFunc: (Double) -> Transform, du let transformAnimation = CAKeyframeAnimation(keyPath: "transform") transformAnimation.duration = duration transformAnimation.values = transformValues - transformAnimation.keyTimes = timeValues as [NSNumber]? transformAnimation.fillMode = kCAFillModeForwards transformAnimation.isRemovedOnCompletion = false return transformAnimation } - -func fixedAngle(_ angle: CGFloat) -> CGFloat { - return angle > -0.0000000000000000000000001 ? angle : CGFloat(2.0 * Double.pi) + angle -} From fb7590bdd1412a878c2e70cb90c9856af511dfa7 Mon Sep 17 00:00:00 2001 From: Alisa Mylnikova Date: Fri, 24 Aug 2018 17:00:26 +0700 Subject: [PATCH 2/2] Move caTimingFunctions away --- .../Examples/Animations/EasingView.swift | 2 +- Macaw.xcodeproj/project.pbxproj | 6 +++ Source/animation/AnimationProducer.swift | 4 +- Source/animation/Easing.swift | 37 +++++-------------- .../MorphingGenerator.swift | 2 +- .../OpacityGenerator.swift | 2 +- .../ShapeAnimationGenerator.swift | 2 +- .../animation_generators/TimingFunction.swift | 25 +++++++++++++ .../TransformGenerator.swift | 4 +- 9 files changed, 48 insertions(+), 36 deletions(-) create mode 100644 Source/animation/types/animation_generators/TimingFunction.swift diff --git a/Example/Example/Examples/Animations/EasingView.swift b/Example/Example/Examples/Animations/EasingView.swift index 098dcd83..7134f2a0 100644 --- a/Example/Example/Examples/Animations/EasingView.swift +++ b/Example/Example/Examples/Animations/EasingView.swift @@ -14,7 +14,7 @@ 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) diff --git a/Macaw.xcodeproj/project.pbxproj b/Macaw.xcodeproj/project.pbxproj index 183e1f2f..2c83be29 100644 --- a/Macaw.xcodeproj/project.pbxproj +++ b/Macaw.xcodeproj/project.pbxproj @@ -448,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 */; }; @@ -847,6 +849,7 @@ 5B6E191E20AC58F900454E7E /* Stroke.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stroke.swift; sourceTree = ""; }; 5B6E191F20AC58F900454E7E /* Color.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; }; 5B6E192020AC58F900454E7E /* Gradient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Gradient.swift; sourceTree = ""; }; + 5B7D7ED221300D4A00B5ED00 /* TimingFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimingFunction.swift; sourceTree = ""; }; 5B7E79CC20CBE69600C50BCF /* masking-path-02-b-manual.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "masking-path-02-b-manual.reference"; sourceTree = ""; }; 5B7E79CD20CBE69700C50BCF /* masking-path-02-b-manual.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "masking-path-02-b-manual.svg"; sourceTree = ""; }; 5B7E79DC20D2781A00C50BCF /* masking-intro-01-f-manual.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "masking-intro-01-f-manual.reference"; sourceTree = ""; }; @@ -1084,6 +1087,7 @@ 57E5E0FB1E3B393900D1CB28 /* MorphingGenerator.swift */, 57A27BD21E44C5570057BD3A /* ShapeAnimationGenerator.swift */, 57E5E0FC1E3B393900D1CB28 /* OpacityGenerator.swift */, + 5B7D7ED221300D4A00B5ED00 /* TimingFunction.swift */, 57E5E0FE1E3B393900D1CB28 /* TransformGenerator.swift */, ); path = animation_generators; @@ -1970,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 */, @@ -2104,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 */, diff --git a/Source/animation/AnimationProducer.swift b/Source/animation/AnimationProducer.swift index 00e3da00..23f8a734 100644 --- a/Source/animation/AnimationProducer.swift +++ b/Source/animation/AnimationProducer.swift @@ -307,7 +307,7 @@ class AnimationProducer { 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.progressForTimingFunction(progress: progress) + let t = animation.easing.progressFor(time: progress) unionBounds = unionBounds?.union(rect: contentsAnimation.getVFunc()(t).group().bounds!) } @@ -378,7 +378,7 @@ class AnimationProducer { continue } - let t = animation.easing.progressForTimingFunction(progress: progress) + let t = animation.easing.progressFor(time: progress) group.contents = animation.getVFunc()(t) animation.onProgressUpdate?(progress) diff --git a/Source/animation/Easing.swift b/Source/animation/Easing.swift index d5354497..18bbc19a 100644 --- a/Source/animation/Easing.swift +++ b/Source/animation/Easing.swift @@ -14,7 +14,7 @@ import AppKit open class Easing { - public static let ease: Easing = Ease() + public static let ease: Easing = Easing() public static let linear: Easing = Easing() public static let easeIn: Easing = EaseIn() public static let easeOut: Easing = EaseOut() @@ -24,44 +24,25 @@ open class Easing { return ElasticInOut(elasticity: elasticity) } - open func caTimingFunction() -> CAMediaTimingFunction { - return CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) - } - - open func progressForTimingFunction(progress: Double) -> Double { - return progress - } -} - -private class Ease: Easing { - override open func caTimingFunction() -> CAMediaTimingFunction { - return CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault) + open func progressFor(time: Double) -> Double { + return time } } private class EaseIn: Easing { - override open func caTimingFunction() -> CAMediaTimingFunction { - return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) - } - override open func progressForTimingFunction(progress t: Double) -> Double { + override open func progressFor(time t: Double) -> Double { return t * t } } private class EaseOut: Easing { - override open func caTimingFunction() -> CAMediaTimingFunction { - return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) - } - override open func progressForTimingFunction(progress t: Double) -> Double { + override open func progressFor(time t: Double) -> Double { return -(t * (t - 2)) } } private class EaseInOut: Easing { - override open func caTimingFunction() -> CAMediaTimingFunction { - return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) - } - override open func progressForTimingFunction(progress t: Double) -> Double { + override open func progressFor(time t: Double) -> Double { if t < 0.5 { return 2.0 * t * t } else { @@ -77,11 +58,11 @@ public class ElasticInOut: Easing { self.elasticity = elasticity } - override open func progressForTimingFunction(progress: Double) -> Double { - if progress == 0 { + override open func progressFor(time: Double) -> Double { + if time == 0 { return 0 } - var t = progress / 0.5 + var t = time / 0.5 if t == 2 { return 1 } diff --git a/Source/animation/types/animation_generators/MorphingGenerator.swift b/Source/animation/types/animation_generators/MorphingGenerator.swift index acc4005c..bf124123 100644 --- a/Source/animation/types/animation_generators/MorphingGenerator.swift +++ b/Source/animation/types/animation_generators/MorphingGenerator.swift @@ -44,7 +44,7 @@ func addMorphingAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, anim renderTransform: layer.renderTransform!) generatedAnim.repeatCount = Float(animation.repeatCount) - generatedAnim.timingFunction = animation.easing.caTimingFunction() + generatedAnim.timingFunction = caTimingFunction(animation.easing) generatedAnim.autoreverses = animation.autoreverses generatedAnim.completion = { finished in diff --git a/Source/animation/types/animation_generators/OpacityGenerator.swift b/Source/animation/types/animation_generators/OpacityGenerator.swift index f0fbc6d2..4404d96a 100644 --- a/Source/animation/types/animation_generators/OpacityGenerator.swift +++ b/Source/animation/types/animation_generators/OpacityGenerator.swift @@ -21,7 +21,7 @@ func addOpacityAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, anima offset: animation.pausedProgress, fps: opacityAnimation.logicalFps) generatedAnimation.repeatCount = Float(animation.repeatCount) - generatedAnimation.timingFunction = animation.easing.caTimingFunction() + generatedAnimation.timingFunction = caTimingFunction(animation.easing) generatedAnimation.completion = { finished in diff --git a/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift b/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift index 147fd692..e67a2aaa 100644 --- a/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift +++ b/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift @@ -43,7 +43,7 @@ func addShapeAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animati renderTransform: layer.renderTransform!) generatedAnim.repeatCount = Float(animation.repeatCount) - generatedAnim.timingFunction = animation.easing.caTimingFunction() + generatedAnim.timingFunction = caTimingFunction(animation.easing) generatedAnim.autoreverses = animation.autoreverses generatedAnim.completion = { finished in diff --git a/Source/animation/types/animation_generators/TimingFunction.swift b/Source/animation/types/animation_generators/TimingFunction.swift new file mode 100644 index 00000000..fd0f7e64 --- /dev/null +++ b/Source/animation/types/animation_generators/TimingFunction.swift @@ -0,0 +1,25 @@ +#if os(iOS) +import UIKit +#elseif os(OSX) +import AppKit +#endif + +func caTimingFunction(_ easing: Easing) -> CAMediaTimingFunction { + if easing === Easing.ease { + return CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault) + } + if easing === Easing.linear { + return CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) + } + if easing === Easing.easeIn { + return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) + } + if easing === Easing.easeOut { + return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) + } + if easing === Easing.easeInOut { + return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + } + return CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) +} + diff --git a/Source/animation/types/animation_generators/TransformGenerator.swift b/Source/animation/types/animation_generators/TransformGenerator.swift index 876d8b1e..ca98ebf5 100644 --- a/Source/animation/types/animation_generators/TransformGenerator.swift +++ b/Source/animation/types/animation_generators/TransformGenerator.swift @@ -87,7 +87,7 @@ func transformAnimationByFunc(_ animation: TransformAnimation, node: Node, durat if let trajectory = animation.trajectory { let pathAnimation = CAKeyframeAnimation(keyPath: "position") - pathAnimation.timingFunction = animation.easing.caTimingFunction() + pathAnimation.timingFunction = caTimingFunction(animation.easing) pathAnimation.duration = duration / 2 pathAnimation.autoreverses = animation.autoreverses let value = AnimationUtils.absoluteTransform(node, pos: valueFunc(0), view: view) @@ -104,7 +104,7 @@ func transformAnimationByFunc(_ animation: TransformAnimation, node: Node, durat var tValue = Array(stride(from: 0.0, to: 1.0, by: step)) tValue.append(1.0) for t in tValue { - let progress = animation.easing.progressForTimingFunction(progress: t) + let progress = animation.easing.progressFor(time: t) let value = AnimationUtils.absoluteTransform(node, pos: valueFunc(offset + progress), view: view) let cgValue = CATransform3DMakeAffineTransform(value.toCG()) transformValues.append(cgValue)