2017-08-10 16:01:11 +03:00
|
|
|
import Foundation
|
|
|
|
|
2017-08-10 14:26:02 +03:00
|
|
|
#if os(iOS)
|
2018-04-04 17:59:28 +03:00
|
|
|
import UIKit
|
2017-08-11 11:08:31 +03:00
|
|
|
#elseif os(OSX)
|
2018-04-04 17:59:28 +03:00
|
|
|
import AppKit
|
2017-08-10 14:26:02 +03:00
|
|
|
#endif
|
2016-04-19 13:56:25 +03:00
|
|
|
|
2016-08-24 13:25:14 +03:00
|
|
|
let animationProducer = AnimationProducer()
|
2016-09-16 15:00:25 +03:00
|
|
|
|
2016-08-18 12:53:33 +03:00
|
|
|
class AnimationProducer {
|
2016-04-20 15:47:44 +03:00
|
|
|
|
2018-11-01 13:30:16 +03:00
|
|
|
var storedAnimations = [Node: BasicAnimation]() // is used to make sure node is in view hierarchy before actually creating the animation
|
2017-02-21 12:55:25 +03:00
|
|
|
var delayedAnimations = [BasicAnimation: Timer]()
|
2017-08-28 06:20:14 +03:00
|
|
|
var displayLink: MDisplayLinkProtocol?
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2016-11-25 13:14:06 +03:00
|
|
|
struct ContentAnimationDesc {
|
|
|
|
let animation: ContentsAnimation
|
|
|
|
let layer: CALayer
|
2017-07-03 10:09:56 +03:00
|
|
|
weak var cache: AnimationCache?
|
2016-11-25 13:14:06 +03:00
|
|
|
let startDate: Date
|
|
|
|
let finishDate: Date
|
2017-10-16 12:11:41 +03:00
|
|
|
let completion: (() -> Void)?
|
2016-11-25 13:14:06 +03:00
|
|
|
}
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2016-11-25 13:14:06 +03:00
|
|
|
var contentsAnimations = [ContentAnimationDesc]()
|
2016-09-19 10:13:31 +03:00
|
|
|
|
2017-10-16 12:11:41 +03:00
|
|
|
func addAnimation(_ animation: BasicAnimation, withoutDelay: Bool = false) {
|
|
|
|
|
2017-01-10 12:45:35 +03:00
|
|
|
// Delay - launching timer
|
2017-10-16 12:11:41 +03:00
|
|
|
if animation.delay > 0.0 && !withoutDelay {
|
2016-09-16 17:22:15 +03:00
|
|
|
|
2017-10-16 12:11:41 +03:00
|
|
|
let timer = Timer.schedule(delay: animation.delay) { [weak self] _ in
|
|
|
|
self?.addAnimation(animation, withoutDelay: true)
|
2017-04-27 11:11:14 +03:00
|
|
|
_ = self?.delayedAnimations.removeValue(forKey: animation)
|
|
|
|
animation.delayed = false
|
2017-10-16 12:11:41 +03:00
|
|
|
}
|
|
|
|
|
2017-04-25 18:07:00 +03:00
|
|
|
animation.delayed = true
|
2017-02-21 12:55:25 +03:00
|
|
|
delayedAnimations[animation] = timer
|
2017-10-16 12:11:41 +03:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
2016-04-20 15:47:44 +03:00
|
|
|
|
2017-01-10 12:45:35 +03:00
|
|
|
// Empty - executing completion
|
2017-10-16 12:11:41 +03:00
|
|
|
if animation.type == .empty {
|
|
|
|
executeCompletion(animation)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-01-10 12:45:35 +03:00
|
|
|
// Cycle - attaching "re-add animation" logic
|
|
|
|
if animation.cycled {
|
|
|
|
if animation.manualStop {
|
|
|
|
return
|
|
|
|
}
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2017-01-10 12:45:35 +03:00
|
|
|
let reAdd = EmptyAnimation {
|
|
|
|
self.addAnimation(animation)
|
|
|
|
}
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2017-01-10 16:23:06 +03:00
|
|
|
if let nextAnimation = animation.next {
|
|
|
|
nextAnimation.next = reAdd
|
|
|
|
} else {
|
|
|
|
animation.next = reAdd
|
|
|
|
}
|
2017-01-10 12:45:35 +03:00
|
|
|
}
|
2016-08-24 17:14:05 +03:00
|
|
|
|
2017-01-10 12:45:35 +03:00
|
|
|
// General case
|
2018-11-01 13:30:16 +03:00
|
|
|
guard let node = animation.node else {
|
2017-10-16 12:11:41 +03:00
|
|
|
return
|
|
|
|
}
|
2018-11-01 13:30:16 +03:00
|
|
|
for observer in node.animationObservers {
|
|
|
|
observer.processAnimation(animation)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch animation.type {
|
|
|
|
case .unknown:
|
|
|
|
return
|
|
|
|
case .empty:
|
|
|
|
executeCompletion(animation)
|
|
|
|
case .sequence:
|
|
|
|
addAnimationSequence(animation)
|
|
|
|
case .combine:
|
|
|
|
addCombineAnimation(animation)
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2018-11-09 08:19:58 +03:00
|
|
|
guard let macawView = animation.nodeRenderer?.view else {
|
2017-10-16 12:11:41 +03:00
|
|
|
storedAnimations[node] = animation
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-08-25 12:32:30 +03:00
|
|
|
guard let layer = macawView.mLayer else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-10-16 12:11:41 +03:00
|
|
|
guard let cache = macawView.animationCache else {
|
|
|
|
return
|
|
|
|
}
|
2016-04-20 15:47:44 +03:00
|
|
|
|
2017-10-16 12:11:41 +03:00
|
|
|
// swiftlint:disable superfluous_disable_command switch_case_alignment
|
|
|
|
switch animation.type {
|
|
|
|
case .affineTransformation:
|
|
|
|
addTransformAnimation(animation, sceneLayer: layer, animationCache: cache, completion: {
|
2016-11-25 13:14:06 +03:00
|
|
|
if let next = animation.next {
|
|
|
|
self.addAnimation(next)
|
|
|
|
}
|
|
|
|
})
|
2017-10-16 12:11:41 +03:00
|
|
|
case .opacity:
|
|
|
|
addOpacityAnimation(animation, sceneLayer: layer, animationCache: cache, completion: {
|
2017-01-24 15:21:21 +03:00
|
|
|
if let next = animation.next {
|
|
|
|
self.addAnimation(next)
|
|
|
|
}
|
|
|
|
})
|
2017-10-16 12:11:41 +03:00
|
|
|
case .contents:
|
|
|
|
addContentsAnimation(animation, cache: cache) {
|
|
|
|
if let next = animation.next {
|
|
|
|
self.addAnimation(next)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case .morphing:
|
|
|
|
addMorphingAnimation(animation, sceneLayer: layer, animationCache: cache) {
|
|
|
|
if let next = animation.next {
|
|
|
|
self.addAnimation(next)
|
|
|
|
}
|
|
|
|
}
|
2017-02-03 16:31:29 +03:00
|
|
|
case .shape:
|
2017-10-16 12:11:41 +03:00
|
|
|
addShapeAnimation(animation, sceneLayer: layer, animationCache: cache) {
|
2017-02-03 16:31:29 +03:00
|
|
|
if let next = animation.next {
|
|
|
|
self.addAnimation(next)
|
|
|
|
}
|
2017-10-16 12:11:41 +03:00
|
|
|
}
|
2018-11-01 13:30:16 +03:00
|
|
|
default:
|
|
|
|
break
|
2017-10-16 12:11:41 +03:00
|
|
|
}
|
|
|
|
// swiftlint:enable superfluous_disable_command switch_case_alignment
|
|
|
|
}
|
|
|
|
|
2017-02-21 12:55:25 +03:00
|
|
|
func removeDelayed(animation: BasicAnimation) {
|
|
|
|
guard let timer = delayedAnimations[animation] else {
|
|
|
|
return
|
|
|
|
}
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2017-02-21 12:55:25 +03:00
|
|
|
timer.invalidate()
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2017-04-25 18:07:00 +03:00
|
|
|
animation.delayed = false
|
2017-02-21 12:55:25 +03:00
|
|
|
delayedAnimations.removeValue(forKey: animation)
|
|
|
|
}
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2016-11-25 13:14:06 +03:00
|
|
|
// MARK: - Sequence animation
|
2018-12-17 14:26:33 +03:00
|
|
|
func addAnimationSequence(_ animationSequnce: Animation) {
|
2017-10-16 12:11:41 +03:00
|
|
|
guard let sequence = animationSequnce as? AnimationSequence else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generating sequence
|
|
|
|
var sequenceAnimations = [BasicAnimation]()
|
2017-01-10 16:23:06 +03:00
|
|
|
var cycleAnimations = sequence.animations
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2017-01-10 16:23:06 +03:00
|
|
|
if sequence.autoreverses {
|
|
|
|
cycleAnimations.append(contentsOf: sequence.animations.reversed())
|
|
|
|
}
|
2017-10-16 12:11:41 +03:00
|
|
|
|
|
|
|
if sequence.repeatCount > 0.0001 {
|
|
|
|
for _ in 0..<Int(sequence.repeatCount) {
|
|
|
|
sequenceAnimations.append(contentsOf: cycleAnimations)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
sequenceAnimations.append(contentsOf: cycleAnimations)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Connecting animations
|
|
|
|
for i in 0..<(sequenceAnimations.count - 1) {
|
|
|
|
let animation = sequenceAnimations[i]
|
|
|
|
animation.next = sequenceAnimations[i + 1]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Completion
|
|
|
|
if let completion = sequence.completion {
|
|
|
|
let completionAnimation = EmptyAnimation(completion: completion)
|
|
|
|
|
|
|
|
if let next = sequence.next {
|
|
|
|
completionAnimation.next = next
|
|
|
|
}
|
|
|
|
|
|
|
|
sequenceAnimations.last?.next = completionAnimation
|
|
|
|
} else {
|
|
|
|
if let next = sequence.next {
|
|
|
|
sequenceAnimations.last?.next = next
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Launching
|
|
|
|
if let firstAnimation = sequence.animations.first {
|
|
|
|
self.addAnimation(firstAnimation)
|
|
|
|
}
|
|
|
|
}
|
2016-08-08 13:23:15 +03:00
|
|
|
|
2016-11-25 13:14:06 +03:00
|
|
|
// MARK: - Empty Animation
|
2017-10-16 12:11:41 +03:00
|
|
|
fileprivate func executeCompletion(_ emptyAnimation: BasicAnimation) {
|
|
|
|
emptyAnimation.completion?()
|
|
|
|
}
|
|
|
|
|
2016-11-25 13:14:06 +03:00
|
|
|
// MARK: - Stored animation
|
2017-10-16 12:11:41 +03:00
|
|
|
func addStoredAnimations(_ node: Node) {
|
|
|
|
if let animation = storedAnimations[node] {
|
|
|
|
addAnimation(animation)
|
|
|
|
storedAnimations.removeValue(forKey: node)
|
|
|
|
}
|
|
|
|
|
|
|
|
guard let group = node as? Group else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
group.contents.forEach { child in
|
|
|
|
addStoredAnimations(child)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-25 13:14:06 +03:00
|
|
|
// MARK: - Contents animation
|
2017-10-16 12:11:41 +03:00
|
|
|
|
|
|
|
func addContentsAnimation(_ animation: BasicAnimation, cache: AnimationCache?, completion: @escaping (() -> Void)) {
|
2016-11-25 13:14:06 +03:00
|
|
|
guard let contentsAnimation = animation as? ContentsAnimation else {
|
|
|
|
return
|
|
|
|
}
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2016-11-25 13:14:06 +03:00
|
|
|
if animation.autoreverses {
|
|
|
|
animation.autoreverses = false
|
|
|
|
addAnimation([animation, animation.reverse()].sequence() as! BasicAnimation)
|
|
|
|
return
|
|
|
|
}
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2016-11-25 13:14:06 +03:00
|
|
|
if animation.repeatCount > 0.0001 {
|
|
|
|
animation.repeatCount = 0.0
|
|
|
|
var animSequence = [Animation]()
|
2017-03-06 20:00:05 +03:00
|
|
|
for _ in 0...Int(animation.repeatCount) {
|
2016-11-25 13:14:06 +03:00
|
|
|
animSequence.append(animation)
|
|
|
|
}
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2016-11-25 13:14:06 +03:00
|
|
|
addAnimation(animSequence.sequence() as! BasicAnimation)
|
|
|
|
return
|
|
|
|
}
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2016-11-25 13:14:06 +03:00
|
|
|
let startDate = Date(timeInterval: contentsAnimation.delay, since: Date())
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2018-08-06 13:50:47 +03:00
|
|
|
var unionBounds = contentsAnimation.getVFunc()(0.0).group().bounds
|
|
|
|
stride(from: 0.0, to: 1.0, by: 0.02).forEach { progress in
|
2018-08-24 13:00:26 +03:00
|
|
|
let t = animation.easing.progressFor(time: progress)
|
2018-08-06 13:50:47 +03:00
|
|
|
unionBounds = unionBounds?.union(rect: contentsAnimation.getVFunc()(t).group().bounds!)
|
2016-11-25 13:14:06 +03:00
|
|
|
}
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2018-11-01 13:30:16 +03:00
|
|
|
guard let renderer = animation.nodeRenderer, let layer = cache?.layerForNodeRenderer(renderer, animation: contentsAnimation, customBounds: unionBounds) else {
|
2017-07-03 10:09:56 +03:00
|
|
|
return
|
|
|
|
}
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2016-11-25 13:14:06 +03:00
|
|
|
let animationDesc = ContentAnimationDesc(
|
|
|
|
animation: contentsAnimation,
|
2017-07-03 10:09:56 +03:00
|
|
|
layer: layer,
|
2016-11-25 13:14:06 +03:00
|
|
|
cache: cache,
|
|
|
|
startDate: Date(),
|
|
|
|
finishDate: Date(timeInterval: contentsAnimation.duration, since: startDate),
|
|
|
|
completion: completion
|
|
|
|
)
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2016-11-25 13:14:06 +03:00
|
|
|
contentsAnimations.append(animationDesc)
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2017-08-25 12:32:30 +03:00
|
|
|
if displayLink == nil {
|
2017-08-28 06:20:14 +03:00
|
|
|
displayLink = MDisplayLink()
|
|
|
|
displayLink?.startUpdates { [weak self] in
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
self?.updateContentAnimations()
|
|
|
|
}
|
|
|
|
}
|
2016-11-25 13:14:06 +03:00
|
|
|
}
|
|
|
|
}
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2016-11-25 13:14:06 +03:00
|
|
|
@objc func updateContentAnimations() {
|
2017-10-16 12:11:41 +03:00
|
|
|
if contentsAnimations.isEmpty {
|
2016-11-25 13:14:06 +03:00
|
|
|
displayLink?.invalidate()
|
|
|
|
displayLink = .none
|
|
|
|
}
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2016-11-25 13:14:06 +03:00
|
|
|
let currentDate = Date()
|
2017-02-20 12:35:03 +03:00
|
|
|
var animationsToRemove = [Animation]()
|
|
|
|
let count = contentsAnimations.count
|
|
|
|
for (index, animationDesc) in contentsAnimations.reversed().enumerated() {
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2016-11-25 13:14:06 +03:00
|
|
|
let animation = animationDesc.animation
|
2018-11-01 13:30:16 +03:00
|
|
|
guard let group = animation.node as? Group, let renderer = animation.nodeRenderer else {
|
2016-11-25 13:14:06 +03:00
|
|
|
continue
|
|
|
|
}
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2017-01-13 14:25:32 +03:00
|
|
|
defer {
|
|
|
|
animationDesc.layer.setNeedsDisplay()
|
|
|
|
animationDesc.layer.displayIfNeeded()
|
|
|
|
}
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2017-02-20 09:15:26 +03:00
|
|
|
let progress = currentDate.timeIntervalSince(animationDesc.startDate) / animation.duration + animation.pausedProgress
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2017-02-16 13:36:43 +03:00
|
|
|
// Completion
|
2016-11-25 13:14:06 +03:00
|
|
|
if progress >= 1.0 {
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2017-01-13 14:25:32 +03:00
|
|
|
// Final update
|
|
|
|
group.contents = animation.getVFunc()(1.0)
|
|
|
|
animation.onProgressUpdate?(1.0)
|
2017-02-23 12:49:50 +03:00
|
|
|
animation.pausedProgress = 0.0
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2017-01-13 14:25:32 +03:00
|
|
|
// Finishing animation
|
2017-02-17 13:25:26 +03:00
|
|
|
if !animation.cycled {
|
|
|
|
animation.completion?()
|
|
|
|
}
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2017-02-20 12:35:03 +03:00
|
|
|
contentsAnimations.remove(at: count - 1 - index)
|
2018-11-01 13:30:16 +03:00
|
|
|
animationDesc.cache?.freeLayer(renderer)
|
2016-11-25 13:14:06 +03:00
|
|
|
animationDesc.completion?()
|
|
|
|
continue
|
|
|
|
}
|
2017-02-16 13:36:43 +03:00
|
|
|
|
2018-08-24 13:00:26 +03:00
|
|
|
let t = animation.easing.progressFor(time: progress)
|
2017-01-13 14:25:32 +03:00
|
|
|
group.contents = animation.getVFunc()(t)
|
2016-11-25 13:14:06 +03:00
|
|
|
animation.onProgressUpdate?(progress)
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2017-02-16 13:36:43 +03:00
|
|
|
// Manual stop
|
2017-02-20 09:15:26 +03:00
|
|
|
if animation.manualStop || animation.paused {
|
|
|
|
defer {
|
2017-02-20 12:35:03 +03:00
|
|
|
contentsAnimations.remove(at: count - 1 - index)
|
2018-11-01 13:30:16 +03:00
|
|
|
animationDesc.cache?.freeLayer(renderer)
|
2017-02-20 09:15:26 +03:00
|
|
|
}
|
2017-10-16 12:11:41 +03:00
|
|
|
|
2017-02-20 09:15:26 +03:00
|
|
|
if animation.manualStop {
|
|
|
|
animation.pausedProgress = 0.0
|
|
|
|
group.contents = animation.getVFunc()(0)
|
|
|
|
} else if animation.paused {
|
|
|
|
animation.pausedProgress = progress
|
|
|
|
}
|
2017-02-16 13:36:43 +03:00
|
|
|
}
|
2016-11-25 13:14:06 +03:00
|
|
|
}
|
|
|
|
}
|
2016-05-17 16:27:31 +03:00
|
|
|
}
|