1
1
mirror of https://github.com/exyte/Macaw.git synced 2024-09-19 17:07:13 +03:00
Macaw/Source/animation/AnimationProducer.swift

395 lines
12 KiB
Swift
Raw Normal View History

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
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?
2016-11-25 13:14:06 +03:00
struct ContentAnimationDesc {
let animation: ContentsAnimation
let layer: CALayer
weak var cache: AnimationCache?
2019-03-22 09:14:45 +03:00
let topLayers: [ShapeLayer]
2016-11-25 13:14:06 +03:00
let startDate: Date
let finishDate: Date
let completion: (() -> Void)?
2016-11-25 13:14:06 +03:00
}
2016-11-25 13:14:06 +03:00
var contentsAnimations = [ContentAnimationDesc]()
func play(_ animation: BasicAnimation, _ context: AnimationContext, withoutDelay: Bool = false) {
2017-01-10 12:45:35 +03:00
// Delay - launching timer
if animation.delay > 0.0 && !withoutDelay {
let timer = Timer.schedule(delay: animation.delay) { [weak self] _ in
self?.play(animation, context, withoutDelay: true)
2017-04-27 11:11:14 +03:00
_ = self?.delayedAnimations.removeValue(forKey: animation)
animation.delayed = false
}
2017-04-25 18:07:00 +03:00
animation.delayed = true
2017-02-21 12:55:25 +03:00
delayedAnimations[animation] = timer
return
}
2016-04-20 15:47:44 +03:00
2017-01-10 12:45:35 +03:00
// Empty - executing completion
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-01-10 12:45:35 +03:00
let reAdd = EmptyAnimation {
self.play(animation, context)
2017-01-10 12:45:35 +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 {
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, context)
2018-11-01 13:30:16 +03:00
case .combine:
addCombineAnimation(animation, context)
2018-11-01 13:30:16 +03:00
default:
break
}
2018-11-09 08:19:58 +03:00
guard let macawView = animation.nodeRenderer?.view else {
storedAnimations[node] = animation
return
}
guard let layer = macawView.mLayer else {
return
}
guard let cache = macawView.animationCache else {
return
}
2016-04-20 15:47:44 +03:00
// swiftlint:disable superfluous_disable_command switch_case_alignment
switch animation.type {
case .affineTransformation:
addTransformAnimation(animation, context, sceneLayer: layer, animationCache: cache, completion: {
2016-11-25 13:14:06 +03:00
if let next = animation.next {
self.play(next, context)
2016-11-25 13:14:06 +03:00
}
})
case .opacity:
addOpacityAnimation(animation, context, sceneLayer: layer, animationCache: cache, completion: {
2017-01-24 15:21:21 +03:00
if let next = animation.next {
self.play(next, context)
2017-01-24 15:21:21 +03:00
}
})
case .contents:
addContentsAnimation(animation, context, cache: cache) {
if let next = animation.next {
self.play(next, context)
}
}
case .morphing:
addMorphingAnimation(animation, context, sceneLayer: layer, animationCache: cache) {
if let next = animation.next {
self.play(next, context)
}
}
2017-02-03 16:31:29 +03:00
case .shape:
addShapeAnimation(animation, context, sceneLayer: layer, animationCache: cache) {
2017-02-03 16:31:29 +03:00
if let next = animation.next {
self.play(next, context)
2017-02-03 16:31:29 +03:00
}
}
2018-11-01 13:30:16 +03:00
default:
break
}
// 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-02-21 12:55:25 +03:00
timer.invalidate()
2017-04-25 18:07:00 +03:00
animation.delayed = false
2017-02-21 12:55:25 +03:00
delayedAnimations.removeValue(forKey: animation)
}
2016-11-25 13:14:06 +03:00
// MARK: - Sequence animation
func addAnimationSequence(_ animationSequnce: Animation,
_ context: AnimationContext) {
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-01-10 16:23:06 +03:00
if sequence.autoreverses {
cycleAnimations.append(contentsOf: sequence.animations.reversed())
}
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.play(firstAnimation, context)
}
}
2016-08-08 13:23:15 +03:00
2016-11-25 13:14:06 +03:00
// MARK: - Empty Animation
fileprivate func executeCompletion(_ emptyAnimation: BasicAnimation) {
emptyAnimation.completion?()
}
2016-11-25 13:14:06 +03:00
// MARK: - Stored animation
func addStoredAnimations(_ node: Node, _ view: MacawView) {
addStoredAnimations(node, AnimationContext())
}
func addStoredAnimations(_ node: Node, _ context: AnimationContext) {
if let animation = storedAnimations[node] {
play(animation, context)
storedAnimations.removeValue(forKey: node)
}
guard let group = node as? Group else {
return
}
group.contents.forEach { child in
addStoredAnimations(child, context)
}
}
2016-11-25 13:14:06 +03:00
// MARK: - Contents animation
func addContentsAnimation(_ animation: BasicAnimation, _ context: AnimationContext, cache: AnimationCache?, completion: @escaping (() -> Void)) {
2016-11-25 13:14:06 +03:00
guard let contentsAnimation = animation as? ContentsAnimation else {
return
}
2016-11-25 13:14:06 +03:00
if animation.autoreverses {
animation.autoreverses = false
play([animation, animation.reverse()].sequence() as! BasicAnimation, context)
2016-11-25 13:14:06 +03:00
return
}
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)
}
play(animSequence.sequence() as! BasicAnimation, context)
2016-11-25 13:14:06 +03:00
return
}
2016-11-25 13:14:06 +03:00
let startDate = Date(timeInterval: contentsAnimation.delay, since: Date())
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)
unionBounds = unionBounds?.union(rect: contentsAnimation.getVFunc()(t).group().bounds!)
2016-11-25 13:14:06 +03:00
}
guard let renderer = animation.nodeRenderer, let layer = cache?.layerForNodeRenderer(renderer, context, animation: contentsAnimation, customBounds: unionBounds) else {
return
}
var rootRenderer: NodeRenderer? = renderer
while rootRenderer?.parentRenderer != nil {
rootRenderer = rootRenderer?.parentRenderer
}
let allRenderers = rootRenderer?.getAllChildrenRecursive()
2019-02-13 15:53:12 +03:00
var animationRenderers = [NodeRenderer]()
if let groupRenderer = renderer as? GroupRenderer {
animationRenderers.append(contentsOf: groupRenderer.getAllChildrenRecursive())
}
let bottomRenderer = animationRenderers.min { $0.zPosition < $1.zPosition }
2019-03-22 09:14:45 +03:00
var topLayers = [ShapeLayer]()
if let bottomRenderer = bottomRenderer, let allRenderers = allRenderers {
for renderer in allRenderers
where !(renderer is GroupRenderer)
&& renderer.zPosition > bottomRenderer.zPosition
&& !animationRenderers.contains(renderer) {
if let layer = cache?.layerForNodeRenderer(renderer, context, animation: contentsAnimation) {
topLayers.append(layer)
}
}
}
2016-11-25 13:14:06 +03:00
let animationDesc = ContentAnimationDesc(
animation: contentsAnimation,
layer: layer,
2016-11-25 13:14:06 +03:00
cache: cache,
2019-03-22 09:14:45 +03:00
topLayers: topLayers,
2016-11-25 13:14:06 +03:00
startDate: Date(),
finishDate: Date(timeInterval: contentsAnimation.duration, since: startDate),
completion: completion
)
2016-11-25 13:14:06 +03:00
contentsAnimations.append(animationDesc)
if displayLink == nil {
2017-08-28 06:20:14 +03:00
displayLink = MDisplayLink()
displayLink?.startUpdates { [weak self] in
DispatchQueue.main.async {
self?.updateContentAnimations(context)
2017-08-28 06:20:14 +03:00
}
}
2016-11-25 13:14:06 +03:00
}
}
func updateContentAnimations(_ context: AnimationContext) {
if contentsAnimations.isEmpty {
2016-11-25 13:14:06 +03:00
displayLink?.invalidate()
displayLink = .none
}
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() {
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-01-13 14:25:32 +03:00
defer {
animationDesc.layer.setNeedsDisplay()
animationDesc.layer.displayIfNeeded()
}
2017-02-20 09:15:26 +03:00
let progress = currentDate.timeIntervalSince(animationDesc.startDate) / animation.duration + animation.pausedProgress
// Completion
2016-11-25 13:14:06 +03:00
if progress >= 1.0 {
2017-01-13 14:25:32 +03:00
// Final update
group.contents = animation.getVFunc()(1.0)
animation.onProgressUpdate?(1.0)
animation.pausedProgress = 0.0
2017-01-13 14:25:32 +03:00
// Finishing animation
if !animation.cycled {
animation.completion?()
}
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?()
2019-03-22 09:14:45 +03:00
for layer in animationDesc.topLayers {
animationDesc.cache?.freeLayer(layer: layer)
}
2016-11-25 13:14:06 +03:00
continue
}
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)
// 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-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
}
}
2019-03-18 13:46:58 +03:00
2019-03-22 09:14:45 +03:00
for layer in animationDesc.topLayers {
layer.setNeedsDisplay()
layer.displayIfNeeded()
}
2016-11-25 13:14:06 +03:00
}
}
2016-05-17 16:27:31 +03:00
}
class AnimationContext {
var rootTransform: Transform?
func getLayoutTransform(_ renderer: NodeRenderer?) -> Transform {
if rootTransform == nil {
2019-04-08 13:29:41 +03:00
if let view = renderer?.view {
rootTransform = view.place
}
}
return rootTransform ?? Transform.identity
}
}