import Foundation import UIKit public enum AnimationCurve { case linear case easeInOut case spring } extension AnimationCurve { func map(_ fraction: CGFloat) -> CGFloat { switch self { case .linear: return fraction case .easeInOut: return bezierPoint(0.42, 0.0, 0.58, 1.0, fraction) case .spring: return bezierPoint(0.23, 1.0, 0.32, 1.0, fraction) } } } open class AnyAnimation { } open class AnimationInterpolator { private let impl: (T, T, CGFloat) -> T init(_ impl: @escaping (T, T, CGFloat) -> T) { self.impl = impl } public func interpolate(from: T, to: T, fraction: CGFloat) -> T { return self.impl(from, to, fraction) } } public protocol AnimationInterpolatable { static var animationInterpolator: AnimationInterpolator { get } } private let CGFloatInterpolator = AnimationInterpolator { from, to, fraction in return from * (1.0 - fraction) + to * fraction } extension CGFloat: AnimationInterpolatable { public static var animationInterpolator: AnimationInterpolator { return CGFloatInterpolator } } private let CGPointInterpolator = AnimationInterpolator { from, to, fraction in return CGPoint( x: CGFloatInterpolator.interpolate(from: from.x, to: to.x, fraction: fraction), y: CGFloatInterpolator.interpolate(from: from.y, to: to.y, fraction: fraction) ) } extension CGPoint: AnimationInterpolatable { public static var animationInterpolator: AnimationInterpolator { return CGPointInterpolator } } #if targetEnvironment(simulator) @_silgen_name("UIAnimationDragCoefficient") func UIAnimationDragCoefficient() -> Float #endif public final class Animation: AnyAnimation { private let from: T private let to: T private let duration: Double private let curve: AnimationCurve private let interpolator: AnimationInterpolator private var startTime: Double? public private(set) var isFinished: Bool = false var didStart: (() -> Void)? public init(from: T, to: T, duration: Double, curve: AnimationCurve) { self.from = from self.to = to #if targetEnvironment(simulator) self.duration = duration * Double(UIAnimationDragCoefficient()) #else self.duration = duration #endif self.curve = curve self.interpolator = T.animationInterpolator } func start() { self.startTime = CACurrentMediaTime() } func update(at timestamp: Double) -> T { guard let startTime = self.startTime else { return self.from } if self.isFinished { return self.to } let fraction = max(0.0, min(1.0, (timestamp - startTime) / self.duration)) if timestamp > startTime + self.duration { self.isFinished = true } if fraction >= 1.0 { return self.to } return self.interpolator.interpolate(from: self.from, to: self.to, fraction: self.curve.map(fraction)) } } public class AnyAnimatedProperty { var didStartAnimation: (() -> Void)? var hasRunningAnimation: Bool { return false } public func update() { } } public final class AnimatedProperty: AnyAnimatedProperty { private var animation: Animation? override var hasRunningAnimation: Bool { return self.animation != nil } public private(set) var value: T public init(_ value: T) { self.value = value } public func animate(to: T, duration: Double, curve: AnimationCurve) { let timestamp = CACurrentMediaTime() let fromValue: T if let animation = self.animation { fromValue = animation.update(at: timestamp) } else { fromValue = self.value } self.animation = Animation(from: fromValue, to: to, duration: duration, curve: curve) self.animation?.start() self.didStartAnimation?() } public func animate(from: T, to: T, duration: Double, curve: AnimationCurve) { self.value = from self.animation = Animation(from: from, to: to, duration: duration, curve: curve) self.animation?.start() self.didStartAnimation?() } public func set(to: T) { self.animation = nil self.value = to } override public func update() { if let animation = self.animation { self.value = animation.update(at: CACurrentMediaTime()) if animation.isFinished { self.animation = nil } } } }