1
1
mirror of https://github.com/exyte/Macaw.git synced 2024-10-26 04:49:57 +03:00

Use layout cache during animation start

This commit is contained in:
Yuri Strot 2019-02-08 20:46:08 +07:00
parent bf79a59f6f
commit ccb125d872
11 changed files with 84 additions and 79 deletions

View File

@ -83,7 +83,7 @@ class BasicAnimation: Animation {
manualStop = false
paused = false
animationProducer.addAnimation(self)
animationProducer.play(self, AnimationContext())
}
override open func stop() {

View File

@ -25,13 +25,13 @@ class AnimationProducer {
var contentsAnimations = [ContentAnimationDesc]()
func addAnimation(_ animation: BasicAnimation, withoutDelay: Bool = false) {
func play(_ animation: BasicAnimation, _ context: AnimationContext, withoutDelay: Bool = false) {
// Delay - launching timer
if animation.delay > 0.0 && !withoutDelay {
let timer = Timer.schedule(delay: animation.delay) { [weak self] _ in
self?.addAnimation(animation, withoutDelay: true)
self?.play(animation, context, withoutDelay: true)
_ = self?.delayedAnimations.removeValue(forKey: animation)
animation.delayed = false
}
@ -55,7 +55,7 @@ class AnimationProducer {
}
let reAdd = EmptyAnimation {
self.addAnimation(animation)
self.play(animation, context)
}
if let nextAnimation = animation.next {
@ -79,9 +79,9 @@ class AnimationProducer {
case .empty:
executeCompletion(animation)
case .sequence:
addAnimationSequence(animation)
addAnimationSequence(animation, context)
case .combine:
addCombineAnimation(animation)
addCombineAnimation(animation, context)
default:
break
}
@ -102,33 +102,33 @@ class AnimationProducer {
// swiftlint:disable superfluous_disable_command switch_case_alignment
switch animation.type {
case .affineTransformation:
addTransformAnimation(animation, sceneLayer: layer, animationCache: cache, completion: {
addTransformAnimation(animation, context, sceneLayer: layer, animationCache: cache, completion: {
if let next = animation.next {
self.addAnimation(next)
self.play(next, context)
}
})
case .opacity:
addOpacityAnimation(animation, sceneLayer: layer, animationCache: cache, completion: {
addOpacityAnimation(animation, context, sceneLayer: layer, animationCache: cache, completion: {
if let next = animation.next {
self.addAnimation(next)
self.play(next, context)
}
})
case .contents:
addContentsAnimation(animation, cache: cache) {
addContentsAnimation(animation, context, cache: cache) {
if let next = animation.next {
self.addAnimation(next)
self.play(next, context)
}
}
case .morphing:
addMorphingAnimation(animation, sceneLayer: layer, animationCache: cache) {
addMorphingAnimation(animation, context, sceneLayer: layer, animationCache: cache) {
if let next = animation.next {
self.addAnimation(next)
self.play(next, context)
}
}
case .shape:
addShapeAnimation(animation, sceneLayer: layer, animationCache: cache) {
addShapeAnimation(animation, context, sceneLayer: layer, animationCache: cache) {
if let next = animation.next {
self.addAnimation(next)
self.play(next, context)
}
}
default:
@ -149,7 +149,8 @@ class AnimationProducer {
}
// MARK: - Sequence animation
func addAnimationSequence(_ animationSequnce: Animation) {
func addAnimationSequence(_ animationSequnce: Animation,
_ context: AnimationContext) {
guard let sequence = animationSequnce as? AnimationSequence else {
return
}
@ -193,7 +194,7 @@ class AnimationProducer {
// Launching
if let firstAnimation = sequence.animations.first {
self.addAnimation(firstAnimation)
self.play(firstAnimation, context)
}
}
@ -203,9 +204,13 @@ class AnimationProducer {
}
// MARK: - Stored animation
func addStoredAnimations(_ node: Node) {
func addStoredAnimations(_ node: Node, _ view: MacawView) {
addStoredAnimations(node, AnimationContext())
}
func addStoredAnimations(_ node: Node, _ context: AnimationContext) {
if let animation = storedAnimations[node] {
addAnimation(animation)
play(animation, context)
storedAnimations.removeValue(forKey: node)
}
@ -214,20 +219,20 @@ class AnimationProducer {
}
group.contents.forEach { child in
addStoredAnimations(child)
addStoredAnimations(child, context)
}
}
// MARK: - Contents animation
func addContentsAnimation(_ animation: BasicAnimation, cache: AnimationCache?, completion: @escaping (() -> Void)) {
func addContentsAnimation(_ animation: BasicAnimation, _ context: AnimationContext, cache: AnimationCache?, completion: @escaping (() -> Void)) {
guard let contentsAnimation = animation as? ContentsAnimation else {
return
}
if animation.autoreverses {
animation.autoreverses = false
addAnimation([animation, animation.reverse()].sequence() as! BasicAnimation)
play([animation, animation.reverse()].sequence() as! BasicAnimation, context)
return
}
@ -238,7 +243,7 @@ class AnimationProducer {
animSequence.append(animation)
}
addAnimation(animSequence.sequence() as! BasicAnimation)
play(animSequence.sequence() as! BasicAnimation, context)
return
}
@ -250,7 +255,7 @@ class AnimationProducer {
unionBounds = unionBounds?.union(rect: contentsAnimation.getVFunc()(t).group().bounds!)
}
guard let renderer = animation.nodeRenderer, let layer = cache?.layerForNodeRenderer(renderer, animation: contentsAnimation, customBounds: unionBounds) else {
guard let renderer = animation.nodeRenderer, let layer = cache?.layerForNodeRenderer(renderer, context, animation: contentsAnimation, customBounds: unionBounds) else {
return
}
@ -338,3 +343,18 @@ class AnimationProducer {
}
}
}
class AnimationContext {
var rootTransform: Transform?
func getLayoutTransform(_ renderer: NodeRenderer?) -> Transform {
if (rootTransform == nil) {
if let view = renderer?.view, let node = view.renderer?.node() {
rootTransform = LayoutHelper.calcTransform(node, view.contentLayout, view.bounds.size.toMacaw())
}
}
return rootTransform ?? Transform.identity
}
}

View File

@ -1,37 +1,21 @@
import Foundation
class AnimationUtils {
class func absolutePosition(_ nodeRenderer: NodeRenderer?) -> Transform {
return AnimationUtils.absoluteTransform(nodeRenderer, pos: nodeRenderer?.node()?.place ?? .identity)
class func absolutePosition(_ nodeRenderer: NodeRenderer?, _ context: AnimationContext) -> Transform {
return AnimationUtils.absoluteTransform(nodeRenderer, context, pos: nodeRenderer?.node()?.place ?? .identity)
}
class func absoluteTransform(_ nodeRenderer: NodeRenderer?, pos: Transform) -> Transform {
class func absoluteTransform(_ nodeRenderer: NodeRenderer?, _ context: AnimationContext, pos: Transform) -> Transform {
var transform = pos
var parentRenderer = nodeRenderer?.parentRenderer
while parentRenderer != nil {
if let canvas = parentRenderer?.node() as? SVGCanvas,
let view = parentRenderer?.view {
let rect = canvas.layout(size: view.bounds.size.toMacaw()).rect()
let canvasTransform = view.contentLayout.layout(rect: rect, into: view.bounds.size.toMacaw()).move(dx: rect.x, dy: rect.y)
transform = canvasTransform.concat(with: transform)
} else if let node = parentRenderer?.node() {
if let node = parentRenderer?.node() {
transform = node.place.concat(with: transform)
}
parentRenderer = parentRenderer?.parentRenderer
}
var rootRenderer = nodeRenderer
while rootRenderer?.parentRenderer != nil {
rootRenderer = rootRenderer?.parentRenderer
}
if let view = rootRenderer?.view, let bounds = rootRenderer?.node()?.bounds {
let a = view.contentLayout.layout(rect: bounds, into: view.bounds.size.toMacaw())
transform = transform.concat(with: a)
}
return transform
return transform.concat(with: context.getLayoutTransform(nodeRenderer))
}
class func absoluteClip(_ nodeRenderer: NodeRenderer?) -> Locus? {

View File

@ -26,7 +26,7 @@ class AnimationCache {
self.sceneLayer = sceneLayer
}
func layerForNodeRenderer(_ renderer: NodeRenderer, animation: Animation, customBounds: Rect? = .none, shouldRenderContent: Bool = true) -> ShapeLayer {
func layerForNodeRenderer(_ renderer: NodeRenderer, _ context: AnimationContext, animation: Animation, customBounds: Rect? = .none, shouldRenderContent: Bool = true) -> ShapeLayer {
guard let node = renderer.node() else {
return ShapeLayer()
@ -63,7 +63,7 @@ class AnimationCache {
layer.renderTransform = CGAffineTransform(translationX: -1.0 * cgRect.origin.x, y: -1.0 * cgRect.origin.y)
let nodeTransform = AnimationUtils.absolutePosition(renderer).toCG()
let nodeTransform = AnimationUtils.absolutePosition(renderer, context).toCG()
layer.transform = CATransform3DMakeAffineTransform(nodeTransform)
// Clip

View File

@ -111,7 +111,7 @@ extension AnimationProducer {
}
// MARK: - Combine animation
func addCombineAnimation(_ combineAnimation: Animation) {
func addCombineAnimation(_ combineAnimation: Animation, _ context: AnimationContext) {
guard let combine = combineAnimation as? CombineAnimation,
let renderer = combine.nodeRenderer,
let view = renderer.view else {
@ -141,7 +141,7 @@ extension AnimationProducer {
}
combine.repeatCount = 0.0
addAnimationSequence(sequence.sequence())
addAnimationSequence(sequence.sequence(), context)
return
}
@ -182,7 +182,7 @@ extension AnimationProducer {
// Launching
animations.forEach { animation in
self.addAnimation(animation)
self.play(animation, context)
}
}

View File

@ -14,7 +14,7 @@ import UIKit
import AppKit
#endif
func addMorphingAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animationCache: AnimationCache?, completion: @escaping (() -> Void)) {
func addMorphingAnimation(_ animation: BasicAnimation, _ context: AnimationContext, sceneLayer: CALayer, animationCache: AnimationCache?, completion: @escaping (() -> Void)) {
guard let morphingAnimation = animation as? MorphingAnimation else {
return
}
@ -31,7 +31,7 @@ func addMorphingAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, anim
let toLocus = morphingAnimation.getVFunc()(animation.autoreverses ? 0.5 : 1.0)
let duration = animation.autoreverses ? animation.getDuration() / 2.0 : animation.getDuration()
guard let layer = animationCache?.layerForNodeRenderer(renderer, animation: animation, shouldRenderContent: false) else {
guard let layer = animationCache?.layerForNodeRenderer(renderer, context, animation: animation, shouldRenderContent: false) else {
return
}
// Creating proper animation

View File

@ -6,7 +6,7 @@ import UIKit
import AppKit
#endif
func addOpacityAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animationCache: AnimationCache?, completion: @escaping (() -> Void)) {
func addOpacityAnimation(_ animation: BasicAnimation, _ context: AnimationContext, sceneLayer: CALayer, animationCache: AnimationCache?, completion: @escaping (() -> Void)) {
guard let opacityAnimation = animation as? OpacityAnimation else {
return
}
@ -58,7 +58,7 @@ func addOpacityAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, anima
animation.onProgressUpdate?(t)
}
if let renderer = animation.nodeRenderer, let layer = animationCache?.layerForNodeRenderer(renderer, animation: animation) {
if let renderer = animation.nodeRenderer, let layer = animationCache?.layerForNodeRenderer(renderer, context, animation: animation) {
let animationId = animation.ID
layer.add(generatedAnimation, forKey: animationId)
animation.removeFunc = { [weak layer] in

View File

@ -14,7 +14,7 @@ import UIKit
import AppKit
#endif
func addShapeAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animationCache: AnimationCache?, completion: @escaping (() -> Void)) {
func addShapeAnimation(_ animation: BasicAnimation, _ context: AnimationContext, sceneLayer: CALayer, animationCache: AnimationCache?, completion: @escaping (() -> Void)) {
guard let shapeAnimation = animation as? ShapeAnimation else {
return
}
@ -30,17 +30,17 @@ func addShapeAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animati
let mutatingShape = SceneUtils.shapeCopy(from: fromShape)
renderer.replaceNode(with: mutatingShape)
guard let layer = animationCache?.layerForNodeRenderer(renderer, animation: animation, shouldRenderContent: false) else {
guard let layer = animationCache?.layerForNodeRenderer(renderer, context, animation: animation, shouldRenderContent: false) else {
return
}
// Creating proper animation
let generatedAnim = generateShapeAnimation(
from: mutatingShape,
to: toShape,
animation: shapeAnimation,
duration: duration,
renderTransform: layer.renderTransform!)
let generatedAnim = generateShapeAnimation(context,
from: mutatingShape,
to: toShape,
animation: shapeAnimation,
duration: duration,
renderTransform: layer.renderTransform!)
generatedAnim.repeatCount = Float(animation.repeatCount)
generatedAnim.timingFunction = caTimingFunction(animation.easing)
@ -127,7 +127,7 @@ func addShapeAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animati
}
}
fileprivate func generateShapeAnimation(from: Shape, to: Shape, animation: ShapeAnimation, duration: Double, renderTransform: CGAffineTransform) -> CAAnimation {
fileprivate func generateShapeAnimation(_ context: AnimationContext, from: Shape, to: Shape, animation: ShapeAnimation, duration: Double, renderTransform: CGAffineTransform) -> CAAnimation {
let group = CAAnimationGroup()
@ -147,7 +147,7 @@ fileprivate func generateShapeAnimation(from: Shape, to: Shape, animation: Shape
// Transform
let scaleAnimation = CABasicAnimation(keyPath: "transform")
scaleAnimation.duration = duration
let parentPos = AnimationUtils.absolutePosition(animation.nodeRenderer?.parentRenderer)
let parentPos = AnimationUtils.absolutePosition(animation.nodeRenderer?.parentRenderer, context)
let fromPos = parentPos.concat(with: from.place)
let toParentPos = animation.toParentGlobalTransfrom
let toPos = toParentPos.concat(with: to.place)

View File

@ -6,7 +6,7 @@ import UIKit
import AppKit
#endif
func addTransformAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animationCache: AnimationCache?, completion: @escaping (() -> Void)) {
func addTransformAnimation(_ animation: BasicAnimation, _ context: AnimationContext, sceneLayer: CALayer, animationCache: AnimationCache?, completion: @escaping (() -> Void)) {
guard let transformAnimation = animation as? TransformAnimation else {
return
}
@ -25,6 +25,7 @@ func addTransformAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, ani
var generatedAnimation: CAAnimation?
generatedAnimation = transformAnimationByFunc(transformAnimation,
context,
node: node,
duration: animation.getDuration(),
offset: animation.pausedProgress,
@ -71,7 +72,7 @@ func addTransformAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, ani
animation.onProgressUpdate?(t)
}
if let renderer = animation.nodeRenderer, let layer = animationCache?.layerForNodeRenderer(renderer, animation: animation) {
if let renderer = animation.nodeRenderer, let layer = animationCache?.layerForNodeRenderer(renderer, context, animation: animation) {
let animationId = animation.ID
layer.add(generatedAnim, forKey: animationId)
animation.removeFunc = { [weak layer] in
@ -80,7 +81,7 @@ func addTransformAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, ani
}
}
func transformAnimationByFunc(_ animation: TransformAnimation, node: Node, duration: Double, offset: Double, fps: UInt) -> CAAnimation {
func transformAnimationByFunc(_ animation: TransformAnimation, _ context: AnimationContext, node: Node, duration: Double, offset: Double, fps: UInt) -> CAAnimation {
let valueFunc = animation.getVFunc()
@ -89,7 +90,7 @@ func transformAnimationByFunc(_ animation: TransformAnimation, node: Node, durat
pathAnimation.timingFunction = caTimingFunction(animation.easing)
pathAnimation.duration = duration / 2
pathAnimation.autoreverses = animation.autoreverses
let value = AnimationUtils.absoluteTransform(animation.nodeRenderer, pos: valueFunc(0))
let value = AnimationUtils.absoluteTransform(animation.nodeRenderer, context, pos: valueFunc(0))
pathAnimation.values = [NSValue(caTransform3D: CATransform3DMakeAffineTransform(value.toCG()))]
pathAnimation.fillMode = MCAMediaTimingFillMode.forwards
pathAnimation.isRemovedOnCompletion = false
@ -104,7 +105,7 @@ func transformAnimationByFunc(_ animation: TransformAnimation, node: Node, durat
tValue.append(1.0)
for t in tValue {
let progress = animation.easing.progressFor(time: t)
let value = AnimationUtils.absoluteTransform(animation.nodeRenderer, pos: valueFunc(offset + progress))
let value = AnimationUtils.absoluteTransform(animation.nodeRenderer, context, pos: valueFunc(offset + progress))
let cgValue = CATransform3DMakeAffineTransform(value.toCG())
transformValues.append(cgValue)
}

View File

@ -144,7 +144,7 @@ public extension Node {
ctx.clear(rect.toCG())
let transform = LayoutHelper.calcTransform(self, layout, size)
ctx.concatenate(transform)
ctx.concatenate(transform.toCG())
renderer.render(in: ctx, force: false, opacity: self.opacity)
let img = MGraphicsGetImageFromCurrentImageContext()

View File

@ -21,7 +21,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
}
if let _ = superview {
animationProducer.addStoredAnimations(node)
animationProducer.addStoredAnimations(node, self)
}
self.setNeedsDisplay()
@ -53,7 +53,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
return
}
animationProducer.addStoredAnimations(node)
animationProducer.addStoredAnimations(node, self)
}
}
@ -64,7 +64,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
return
}
animationProducer.addStoredAnimations(node)
animationProducer.addStoredAnimations(node, self)
}
override open var intrinsicContentSize: CGSize {
@ -571,14 +571,14 @@ class LayoutHelper {
return CGAffineTransform.identity
}
public class func calcTransform(_ node: Node, _ layout: ContentLayout, _ size: Size) -> CGAffineTransform {
public class func calcTransform(_ node: Node, _ layout: ContentLayout, _ size: Size) -> Transform {
if let canvas = node as? SVGCanvas {
return layout.layout(size: canvas.layout(size: size), into: size).toCG()
return layout.layout(size: canvas.layout(size: size), into: size)
}
if let rect = node.bounds {
return layout.layout(rect: rect, into: size).toCG()
return layout.layout(rect: rect, into: size)
}
return CGAffineTransform.identity
return Transform.identity
}
public func nodeChanged() {